From c261875cf167e6858d052dc983fb0dcb03e3ea40 Mon Sep 17 00:00:00 2001 From: Christian Wenk Date: Thu, 21 Apr 2016 13:37:48 +0200 Subject: [PATCH 01/85] Remove 'next_url' from kwargs before passing it to the cls constructor. The 'next_url' argument causes problems in the _construct_url method if it doesn't belong there. E.g. if you list all projects, change an attribute of a project and then try to save it, the _construct_url will use the 'next_url' from the list method and the save will fail. --- gitlab/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 6c7519537..2f4b46466 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -281,7 +281,7 @@ def _raw_list(self, path, cls, **kwargs): get_all_results = kwargs.get('all', False) # Remove parameters from kwargs before passing it to constructor - for key in ['all', 'page', 'per_page', 'sudo']: + for key in ['all', 'page', 'per_page', 'sudo', 'next_url']: if key in cls_kwargs: del cls_kwargs[key] @@ -385,7 +385,7 @@ def list(self, obj_class, **kwargs): get_all_results = params.get('all', False) # Remove parameters from kwargs before passing it to constructor - for key in ['all', 'page', 'per_page', 'sudo']: + for key in ['all', 'page', 'per_page', 'sudo', 'next_url']: if key in cls_kwargs: del cls_kwargs[key] From d4e2cd6c618d137df645c182271f67c5ae7e8ff5 Mon Sep 17 00:00:00 2001 From: Kris Gambirazzi Date: Wed, 27 Apr 2016 11:56:33 +1200 Subject: [PATCH 02/85] list projects under group --- gitlab/objects.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 2ceb37f71..15799e389 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -717,6 +717,19 @@ def search(self, query, **kwargs): url = '/groups?search=' + query return self.gitlab._raw_list(url, self.obj_cls, **kwargs) + def list_projects(self, gid, **kwargs): + """List all projects in a group + + Attrs: + gid (int): ID of the group + + Raises: + GitlabConnectionError: if the server cannot be reached. + GitlabListError: If the server fails to perform the request. + """ + url = '/groups/%d/projects' % gid + return self.gitlab._raw_list(url, self.obj_cls, **kwargs) + class Hook(GitlabObject): _url = '/hooks' From cd13aff8a0df9136ba3e289fbccd85de3f159bb5 Mon Sep 17 00:00:00 2001 From: Kris Gambirazzi Date: Wed, 27 Apr 2016 11:58:27 +1200 Subject: [PATCH 03/85] update docblock --- gitlab/objects.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 15799e389..3af2b7a54 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -718,14 +718,13 @@ def search(self, query, **kwargs): return self.gitlab._raw_list(url, self.obj_cls, **kwargs) def list_projects(self, gid, **kwargs): - """List all projects in a group + """List projects in a group Attrs: gid (int): ID of the group - Raises: - GitlabConnectionError: if the server cannot be reached. - GitlabListError: If the server fails to perform the request. + Returns: + list(Group): a list of projects in the group """ url = '/groups/%d/projects' % gid return self.gitlab._raw_list(url, self.obj_cls, **kwargs) From d42687db9f0c58ea8a08532fbf6c524b0cc5ed17 Mon Sep 17 00:00:00 2001 From: Ivica Arsov Date: Fri, 20 May 2016 18:39:04 +0200 Subject: [PATCH 04/85] Add support for subscribe and unsubscribe in issues --- gitlab/exceptions.py | 8 ++++++++ gitlab/objects.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 49a3728e7..3fb0613e5 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -91,6 +91,14 @@ class GitlabUnblockError(GitlabOperationError): pass +class GitlabSubscribeError(GitlabOperationError): + pass + + +class GitlabUnsubscribeError(GitlabOperationError): + pass + + class GitlabMRForbiddenError(GitlabOperationError): pass diff --git a/gitlab/objects.py b/gitlab/objects.py index 9c6197c0c..a865ad4dc 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -998,6 +998,22 @@ def Note(self, id=None, **kwargs): issue_id=self.id, **kwargs) + def subscribe(self, **kwargs): + url = ('/projects/%(project_id)s/issues/%(issue_id)s/subscription' % + {'project_id': self.project_id, 'issue_id': self.id}) + + r = self.gitlab._raw_post(url, **kwargs) + raise_error_from_response(r, GitlabSubscribeError) + self._set_from_dict(r.json()) + + def unsubscribe(self, **kwargs): + url = ('/projects/%(project_id)s/issues/%(issue_id)s/subscription' % + {'project_id': self.project_id, 'issue_id': self.id}) + + r = self.gitlab._raw_delete(url, **kwargs) + raise_error_from_response(r, GitlabUnsubscribeError) + self._set_from_dict(r.json()) + class ProjectIssueManager(BaseManager): obj_cls = ProjectIssue From 1b14f5c9b1ff0af083abedff80eafb9adcae629c Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 28 May 2016 09:59:26 +0200 Subject: [PATCH 05/85] project issue: doc and CLI for (un)subscribe --- gitlab/cli.py | 16 ++++++++++++++++ gitlab/objects.py | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/gitlab/cli.py b/gitlab/cli.py index c7dacebd0..181cc564e 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -41,6 +41,8 @@ 'blob': {'required': ['id', 'project-id', 'filepath']}, 'builds': {'required': ['id', 'project-id']}}, + gitlab.ProjectIssue: {'subscribe': {'required': ['id', 'project-id']}, + 'unsubscribe': {'required': ['id', 'project-id']}}, gitlab.ProjectMergeRequest: { 'closes-issues': {'required': ['id', 'project-id']}, 'cancel': {'required': ['id', 'project-id']}, @@ -248,6 +250,20 @@ def do_project_build_retry(self, cls, gl, what, args): except Exception as e: _die("Impossible to retry project build (%s)" % str(e)) + def do_project_issue_subscribe(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.subscribe() + except Exception as e: + _die("Impossible to subscribe to issue (%s)" % str(e)) + + def do_project_issue_unsubscribe(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.unsubscribe() + except Exception as e: + _die("Impossible to subscribe to issue (%s)" % str(e)) + def do_project_merge_request_closesissues(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/objects.py b/gitlab/objects.py index a865ad4dc..313ed964e 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -999,6 +999,12 @@ def Note(self, id=None, **kwargs): **kwargs) def subscribe(self, **kwargs): + """Subscribe to an issue. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabSubscribeError: If the subscription cannot be done + """ url = ('/projects/%(project_id)s/issues/%(issue_id)s/subscription' % {'project_id': self.project_id, 'issue_id': self.id}) @@ -1007,6 +1013,12 @@ def subscribe(self, **kwargs): self._set_from_dict(r.json()) def unsubscribe(self, **kwargs): + """Unsubscribe an issue. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabSubscribeError: If the unsubscription cannot be done + """ url = ('/projects/%(project_id)s/issues/%(issue_id)s/subscription' % {'project_id': self.project_id, 'issue_id': self.id}) From e9e48b9188e00298573bb2f407a854c8bf8a6dff Mon Sep 17 00:00:00 2001 From: Peter Mosmans Date: Mon, 30 May 2016 13:05:58 +1000 Subject: [PATCH 06/85] Added support for HTTP basic authentication --- gitlab/__init__.py | 46 ++++++++++++++++++++++++++++++++++------------ gitlab/config.py | 10 ++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 3320bba81..90ffa5090 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -62,7 +62,8 @@ class Gitlab(object): ssl_verify (bool): Whether SSL certificates should be validated. timeout (float or tuple(float,float)): Timeout to use for requests to the GitLab server. - + http_username: (str): Username for HTTP authentication + http_password: (str): Password for HTTP authentication Attributes: user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users @@ -108,8 +109,9 @@ class Gitlab(object): teams (TeamManager): Manager for GitLab teams """ - def __init__(self, url, private_token=None, - email=None, password=None, ssl_verify=True, timeout=None): + def __init__(self, url, private_token=None, email=None, password=None, + ssl_verify=True, http_username=None, http_password=None, + timeout=None): self._url = '%s/api/v3' % url #: Timeout to use for requests to gitlab server @@ -123,6 +125,8 @@ def __init__(self, url, private_token=None, self.password = password #: Whether SSL certificates should be validated self.ssl_verify = ssl_verify + self.http_username = http_username + self.http_password = http_password #: Create a session object for requests self.session = requests.Session() @@ -176,7 +180,9 @@ def from_config(gitlab_id=None, config_files=None): config = gitlab.config.GitlabConfigParser(gitlab_id=gitlab_id, config_files=config_files) return Gitlab(config.url, private_token=config.token, - ssl_verify=config.ssl_verify, timeout=config.timeout) + ssl_verify=config.ssl_verify, timeout=config.timeout, + http_username=config.http_username, + http_password=config.http_password) def auth(self): """Performs an authentication. @@ -264,13 +270,15 @@ def set_credentials(self, email, password): def _raw_get(self, path, content_type=None, **kwargs): url = '%s%s' % (self._url, path) headers = self._create_headers(content_type) - try: return self.session.get(url, params=kwargs, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -307,7 +315,10 @@ def _raw_post(self, path, data=None, content_type=None, **kwargs): return self.session.post(url, params=kwargs, data=data, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -320,7 +331,10 @@ def _raw_put(self, path, data=None, content_type=None, **kwargs): return self.session.put(url, data=data, params=kwargs, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -334,7 +348,10 @@ def _raw_delete(self, path, content_type=None, **kwargs): params=kwargs, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -374,11 +391,13 @@ def list(self, obj_class, **kwargs): # Also remove the next-url attribute that make queries fail if 'next_url' in params: del params['next_url'] - try: r = self.session.get(url, params=params, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -445,7 +464,10 @@ def get(self, obj_class, id=None, **kwargs): try: r = self.session.get(url, params=params, headers=headers, - verify=self.ssl_verify, timeout=self.timeout) + verify=self.ssl_verify, timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) diff --git a/gitlab/config.py b/gitlab/config.py index 4d0abb841..3ef2efb03 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -78,3 +78,13 @@ def __init__(self, gitlab_id=None, config_files=None): self.timeout = self._config.getint(self.gitlab_id, 'timeout') except Exception: pass + + self.http_username = None + self.http_password = None + try: + self.http_username = self._config.get(self.gitlab_id, + 'http_username') + self.http_password = self._config.get(self.gitlab_id, + 'http_password') + except Exception: + pass From 62203086b04264f04cf6e6681e132ed355bb9b87 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 30 May 2016 20:55:54 +0200 Subject: [PATCH 07/85] add HTTP auth options to doc --- docs/cli.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli.rst b/docs/cli.rst index 81d308d96..7721f54d5 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -78,6 +78,10 @@ section. - URL for the GitLab server * - ``private_token`` - Your user token. Login/password is not supported. + * - ``http_username`` + - Username for optional HTTP authentication + * - ``http_password`` + - Password for optional HTTP authentication CLI === From 59ed4fc07947d80352f1656c5d8a280cddec8b0f Mon Sep 17 00:00:00 2001 From: Peter Mosmans Date: Tue, 31 May 2016 12:10:02 +1000 Subject: [PATCH 08/85] Added HTTPauth support for even more methods :) --- gitlab/__init__.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 90ffa5090..28c5a17a4 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -529,7 +529,10 @@ def delete(self, obj, id=None, **kwargs): params=params, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -576,7 +579,10 @@ def create(self, obj, **kwargs): r = self.session.post(url, data=data, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) @@ -626,7 +632,10 @@ def update(self, obj, **kwargs): r = self.session.put(url, data=data, headers=headers, verify=self.ssl_verify, - timeout=self.timeout) + timeout=self.timeout, + auth=requests.auth.HTTPBasicAuth( + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) From 40d796988171967570d485d7ab709ad6ea466ecf Mon Sep 17 00:00:00 2001 From: Peter Mosmans Date: Tue, 31 May 2016 12:15:11 +1000 Subject: [PATCH 09/85] pylint fix --- gitlab/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 28c5a17a4..c42acaf46 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -581,8 +581,8 @@ def create(self, obj, **kwargs): verify=self.ssl_verify, timeout=self.timeout, auth=requests.auth.HTTPBasicAuth( - self.http_username, - self.http_password)) + self.http_username, + self.http_password)) except Exception as e: raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) From b3e0974451b49ab64866dc131bff59e5471ea620 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Tue, 7 Jun 2016 21:39:15 +0200 Subject: [PATCH 10/85] Add support for build artifacts and trace Fixes #122 --- gitlab/cli.py | 18 +++++++++++++++++- gitlab/objects.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index 181cc564e..bbd2ac4b2 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -36,7 +36,9 @@ gitlab.ProjectBranch: {'protect': {'required': ['id', 'project-id']}, 'unprotect': {'required': ['id', 'project-id']}}, gitlab.ProjectBuild: {'cancel': {'required': ['id', 'project-id']}, - 'retry': {'required': ['id', 'project-id']}}, + 'retry': {'required': ['id', 'project-id']}, + 'artifacts': {'required': ['id', 'project-id']}, + 'trace': {'required': ['id', 'project-id']}}, gitlab.ProjectCommit: {'diff': {'required': ['id', 'project-id']}, 'blob': {'required': ['id', 'project-id', 'filepath']}, @@ -250,6 +252,20 @@ def do_project_build_retry(self, cls, gl, what, args): except Exception as e: _die("Impossible to retry project build (%s)" % str(e)) + def do_project_build_artifacts(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + return o.artifacts() + except Exception as e: + _die("Impossible to get project build artifacts (%s)" % str(e)) + + def do_project_build_trace(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + return o.trace() + except Exception as e: + _die("Impossible to get project build trace (%s)" % str(e)) + def do_project_issue_subscribe(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/objects.py b/gitlab/objects.py index 313ed964e..e522acc51 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -806,18 +806,48 @@ class ProjectBuild(GitlabObject): canUpdate = False canCreate = False - def cancel(self): + def cancel(self, **kwargs): """Cancel the build.""" url = '/projects/%s/builds/%s/cancel' % (self.project_id, self.id) r = self.gitlab._raw_post(url) raise_error_from_response(r, GitlabBuildCancelError, 201) - def retry(self): + def retry(self, **kwargs): """Retry the build.""" url = '/projects/%s/builds/%s/retry' % (self.project_id, self.id) r = self.gitlab._raw_post(url) raise_error_from_response(r, GitlabBuildRetryError, 201) + def artifacts(self, **kwargs): + """Get the build artifacts. + + Returns: + str: The artifacts. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the artifacts are not available. + """ + url = '/projects/%s/builds/%s/artifacts' % (self.project_id, self.id) + r = self.gitlab._raw_get(url) + raise_error_from_response(r, GitlabGetError, 200) + return r.content + + def trace(self, **kwargs): + """Get the build trace. + + Returns: + str: The trace. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the trace is not available. + """ + url = '/projects/%s/builds/%s/trace' % (self.project_id, self.id) + r = self.gitlab._raw_get(url) + raise_error_from_response(r, GitlabGetError, 200) + return r.content + class ProjectBuildManager(BaseManager): obj_cls = ProjectBuild From c3054592f79caa782ec79816501335e9a5c4e9ed Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Wed, 8 Jun 2016 22:30:55 +0200 Subject: [PATCH 11/85] docs: start a FAQ --- docs/faq.rst | 15 +++++++++++++++ docs/index.rst | 1 + 2 files changed, 16 insertions(+) create mode 100644 docs/faq.rst diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 000000000..7c35567bd --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,15 @@ +### +FAQ +### + +How can I close or reopen an issue? +=================================== + + +Set the issue ``state_event`` attribute to ``close`` or ``reopen`` and save the object: + +.. code-block:: python + + issue = my_project.issues.get(issue_id) + issue.state_event = 'close' + issue.save() diff --git a/docs/index.rst b/docs/index.rst index 0d09a780d..2fa45da03 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Contents: install cli api-usage + faq upgrade-from-0.10 api/modules From bea8ea9d0fa921cc5c4fdd1b948420f1f780770c Mon Sep 17 00:00:00 2001 From: "Stefan K. Dunkler" Date: Sat, 11 Jun 2016 17:19:18 +0200 Subject: [PATCH 12/85] Fix that --title is a required argument, when trying to update a ProjectMilestone --- gitlab/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 313ed964e..e19b95149 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1226,6 +1226,7 @@ class ProjectMilestone(GitlabObject): requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] optionalCreateAttrs = ['description', 'due_date', 'state_event'] + optionalUpdateAttrs = requiredCreateAttrs + optionalCreateAttrs shortPrintAttr = 'title' def issues(self): From c24f0d9a3664c025e3284e056d5b4c007dcf5435 Mon Sep 17 00:00:00 2001 From: "Stefan K. Dunkler" Date: Sat, 11 Jun 2016 17:19:18 +0200 Subject: [PATCH 13/85] Fix --title is not a required argument anymore --- gitlab/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 313ed964e..e19b95149 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1226,6 +1226,7 @@ class ProjectMilestone(GitlabObject): requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] optionalCreateAttrs = ['description', 'due_date', 'state_event'] + optionalUpdateAttrs = requiredCreateAttrs + optionalCreateAttrs shortPrintAttr = 'title' def issues(self): From eb6c26f51131fa171c71c19c28448e736f2f5243 Mon Sep 17 00:00:00 2001 From: Missionrulz Date: Sun, 19 Jun 2016 17:39:57 +1200 Subject: [PATCH 14/85] move into own class & create manager class --- gitlab/objects.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 3af2b7a54..02cd756e4 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -717,6 +717,12 @@ def search(self, query, **kwargs): url = '/groups?search=' + query return self.gitlab._raw_list(url, self.obj_cls, **kwargs) + +class GroupProjectManager(BaseManager): + obj_cls = GroupProject + + +class GroupProject(GitlabObject): def list_projects(self, gid, **kwargs): """List projects in a group From 8f707acd0fc77645860c441511126e0a7a2c8a47 Mon Sep 17 00:00:00 2001 From: Missionrulz Date: Sun, 19 Jun 2016 17:46:01 +1200 Subject: [PATCH 15/85] add to __init__.py & move manager after class declaration --- gitlab/__init__.py | 2 ++ gitlab/objects.py | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 6c7519537..e0ddc10ba 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -65,6 +65,7 @@ class Gitlab(object): Attributes: user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users + group_projects (GroupProjectManagers): Manager for GitLab group projects group_members (GroupMemberManager): Manager for GitLab group members groups (GroupManager): Manager for GitLab members hooks (HookManager): Manager for GitLab hooks @@ -125,6 +126,7 @@ def __init__(self, url, private_token=None, self.settings = ApplicationSettingsManager(self) self.user_keys = UserKeyManager(self) self.users = UserManager(self) + self.group_projects = GroupProjectManager(self) self.group_members = GroupMemberManager(self) self.groups = GroupManager(self) self.hooks = HookManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 02cd756e4..fb3c56dec 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -718,10 +718,6 @@ def search(self, query, **kwargs): return self.gitlab._raw_list(url, self.obj_cls, **kwargs) -class GroupProjectManager(BaseManager): - obj_cls = GroupProject - - class GroupProject(GitlabObject): def list_projects(self, gid, **kwargs): """List projects in a group @@ -736,6 +732,10 @@ def list_projects(self, gid, **kwargs): return self.gitlab._raw_list(url, self.obj_cls, **kwargs) +class GroupProjectManager(BaseManager): + obj_cls = GroupProject + + class Hook(GitlabObject): _url = '/hooks' canUpdate = False From d9b9f92bfa26fc69406efd32fe1cfa7929d6b667 Mon Sep 17 00:00:00 2001 From: Missionrulz Date: Sun, 19 Jun 2016 17:49:42 +1200 Subject: [PATCH 16/85] typo in doc block --- gitlab/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index e0ddc10ba..08b74843e 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -65,7 +65,7 @@ class Gitlab(object): Attributes: user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users - group_projects (GroupProjectManagers): Manager for GitLab group projects + group_projects (GroupProjectManager): Manager for GitLab group projects group_members (GroupMemberManager): Manager for GitLab group members groups (GroupManager): Manager for GitLab members hooks (HookManager): Manager for GitLab hooks From 68d15fdfd7cd92adbf54873b75c42e46f35dd918 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 16:08:01 +0200 Subject: [PATCH 17/85] Make GroupProject more "python-gitlabish" --- gitlab/objects.py | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index e56bfc4f2..83a8c8ffb 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -666,6 +666,20 @@ class GroupMemberManager(BaseManager): obj_cls = GroupMember +class GroupProject(GitlabObject): + _url = '/groups/%(group_id)s/projects' + canGet = 'from_list' + canCreate = False + canDelete = False + canUpdate = False + optionalListAttrs = ['archived', 'visibility', 'order_by', 'sort', + 'search', 'ci_enabled_first'] + + +class GroupProjectManager(BaseManager): + obj_cls = GroupProject + + class Group(GitlabObject): _url = '/groups' canUpdate = False @@ -673,7 +687,8 @@ class Group(GitlabObject): requiredCreateAttrs = ['name', 'path'] optionalCreateAttrs = ['description', 'visibility_level'] shortPrintAttr = 'name' - managers = [('members', GroupMemberManager, [('group_id', 'id')])] + managers = [('members', GroupMemberManager, [('group_id', 'id')]), + ('projects', GroupProjectManager, [('group_id', 'id')])] GUEST_ACCESS = 10 REPORTER_ACCESS = 20 @@ -725,24 +740,6 @@ def search(self, query, **kwargs): return self.gitlab._raw_list(url, self.obj_cls, **kwargs) -class GroupProject(GitlabObject): - def list_projects(self, gid, **kwargs): - """List projects in a group - - Attrs: - gid (int): ID of the group - - Returns: - list(Group): a list of projects in the group - """ - url = '/groups/%d/projects' % gid - return self.gitlab._raw_list(url, self.obj_cls, **kwargs) - - -class GroupProjectManager(BaseManager): - obj_cls = GroupProject - - class Hook(GitlabObject): _url = '/hooks' canUpdate = False From 0b8ed5a1687f3b5704b516c1a0ded458ed4a9087 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 18:31:52 +0200 Subject: [PATCH 18/85] commit status: add optional context url --- gitlab/objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 83a8c8ffb..b6a9ddaf0 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -910,7 +910,8 @@ class ProjectCommitStatus(GitlabObject): canDelete = False requiredUrlAttrs = ['project_id', 'commit_id'] requiredCreateAttrs = ['state'] - optionalCreateAttrs = ['description', 'name', 'ref', 'target_url'] + optionalCreateAttrs = ['description', 'name', 'context', 'ref', + 'target_url'] class ProjectCommitStatusManager(BaseManager): From 547f38501177a8d67d35e0d55ca0f2dff38f2904 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 18:34:19 +0200 Subject: [PATCH 19/85] commit status: optional get attrs --- gitlab/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index b6a9ddaf0..4be5574e0 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -909,6 +909,7 @@ class ProjectCommitStatus(GitlabObject): canUpdate = False canDelete = False requiredUrlAttrs = ['project_id', 'commit_id'] + optionalGetAttrs = ['ref_name', 'stage', 'name', 'all'] requiredCreateAttrs = ['state'] optionalCreateAttrs = ['description', 'name', 'context', 'ref', 'target_url'] From 412e2bc7e00a5229974388f795caefa1f0896273 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 19:06:54 +0200 Subject: [PATCH 20/85] Add support for commit comments https://github.com/gitlabhq/gitlabhq/blob/master/doc/api/commits.md --- gitlab/__init__.py | 3 +++ gitlab/objects.py | 46 +++++++++++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 1f895e36b..a8e0fe6a6 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -77,6 +77,8 @@ class Gitlab(object): branches project_commits (ProjectCommitManager): Manager for GitLab projects commits + project_commitcomments (ProjectCommitCommentManager): Manager for + GitLab projects commits comments project_keys (ProjectKeyManager): Manager for GitLab projects keys project_events (ProjectEventManager): Manager for GitLab projects events @@ -143,6 +145,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.licenses = LicenseManager(self) self.project_branches = ProjectBranchManager(self) self.project_commits = ProjectCommitManager(self) + self.project_commit_comments = ProjectCommitCommentManager(self) self.project_keys = ProjectKeyManager(self) self.project_events = ProjectEventManager(self) self.project_forks = ProjectForkManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 4be5574e0..7bba1c97e 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -838,6 +838,35 @@ class ProjectBuildManager(BaseManager): obj_cls = ProjectBuild +class ProjectCommitStatus(GitlabObject): + _url = '/projects/%(project_id)s/statuses/%(commit_id)s' + canUpdate = False + canDelete = False + requiredUrlAttrs = ['project_id', 'commit_id'] + optionalGetAttrs = ['ref_name', 'stage', 'name', 'all'] + requiredCreateAttrs = ['state'] + optionalCreateAttrs = ['description', 'name', 'context', 'ref', + 'target_url'] + + +class ProjectCommitStatusManager(BaseManager): + obj_cls = ProjectCommitStatus + + +class ProjectCommitComment(GitlabObject): + _url = '/projects/%(project_id)s/repository/commits/%(commit_id)s/comments' + canUpdate = False + cantGet = False + canDelete = False + requiredUrlAttrs = ['project_id', 'commit_id'] + requiredCreateAttrs = ['note'] + optionalCreateAttrs = ['path', 'line', 'line_type'] + + +class ProjectCommitCommentManager(BaseManager): + obj_cls = ProjectCommitComment + + class ProjectCommit(GitlabObject): _url = '/projects/%(project_id)s/repository/commits' canDelete = False @@ -845,6 +874,8 @@ class ProjectCommit(GitlabObject): canCreate = False requiredUrlAttrs = ['project_id'] shortPrintAttr = 'title' + managers = [('comments', ProjectCommitCommentManager, + [('project_id', 'project_id'), ('commit_id', 'id')])] def diff(self, **kwargs): """Generate the commit diff.""" @@ -904,21 +935,6 @@ class ProjectCommitManager(BaseManager): obj_cls = ProjectCommit -class ProjectCommitStatus(GitlabObject): - _url = '/projects/%(project_id)s/statuses/%(commit_id)s' - canUpdate = False - canDelete = False - requiredUrlAttrs = ['project_id', 'commit_id'] - optionalGetAttrs = ['ref_name', 'stage', 'name', 'all'] - requiredCreateAttrs = ['state'] - optionalCreateAttrs = ['description', 'name', 'context', 'ref', - 'target_url'] - - -class ProjectCommitStatusManager(BaseManager): - obj_cls = ProjectCommitStatus - - class ProjectKey(GitlabObject): _url = '/projects/%(project_id)s/keys' canUpdate = False From c85276a6e6c5088ea6f2ecb13059488c9779ea2c Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 19:37:30 +0200 Subject: [PATCH 21/85] issues: add optional listing parameters --- gitlab/objects.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 7bba1c97e..41b829544 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -760,6 +760,7 @@ class Issue(GitlabObject): canUpdate = False canCreate = False shortPrintAttr = 'title' + optionalListAttrs = ['state', 'labels'] class IssueManager(BaseManager): @@ -1003,6 +1004,7 @@ class ProjectIssue(GitlabObject): _url = '/projects/%(project_id)s/issues/' _constructorTypes = {'author': 'User', 'assignee': 'User', 'milestone': 'ProjectMilestone'} + optionalListAttrs = ['state', 'labels', 'milestone', 'iid'] requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] # FIXME: state_event is only valid with update From 60c99108646c5913a2d477e96b78556528d25f35 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 19:39:51 +0200 Subject: [PATCH 22/85] issues: add missing optional listing parameters --- gitlab/objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 83569a0c1..fe69fff64 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -760,7 +760,7 @@ class Issue(GitlabObject): canUpdate = False canCreate = False shortPrintAttr = 'title' - optionalListAttrs = ['state', 'labels'] + optionalListAttrs = ['state', 'labels', 'order_by', 'sort'] class IssueManager(BaseManager): @@ -1034,7 +1034,8 @@ class ProjectIssue(GitlabObject): _url = '/projects/%(project_id)s/issues/' _constructorTypes = {'author': 'User', 'assignee': 'User', 'milestone': 'ProjectMilestone'} - optionalListAttrs = ['state', 'labels', 'milestone', 'iid'] + optionalListAttrs = ['state', 'labels', 'milestone', 'iid', 'order_by', + 'sort'] requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] # FIXME: state_event is only valid with update From ca68f6de561cbe5f1e528260eff30ff5943cd39e Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 20:12:52 +0200 Subject: [PATCH 23/85] project issue: proper update attributes --- gitlab/objects.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index fe69fff64..bdadd385e 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1038,9 +1038,11 @@ class ProjectIssue(GitlabObject): 'sort'] requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title'] - # FIXME: state_event is only valid with update optionalCreateAttrs = ['description', 'assignee_id', 'milestone_id', - 'labels', 'state_event'] + 'labels', 'created_at'] + optionalUpdateAttrs = ['title', 'description', 'assignee_id', + 'milestone_id', 'labels', 'created_at', + 'state_event'] shortPrintAttr = 'title' managers = [('notes', ProjectIssueNoteManager, [('project_id', 'project_id'), ('issue_id', 'id')])] @@ -1054,7 +1056,8 @@ def _data_for_gitlab(self, extra_parameters={}, update=False): labels = ", ".join(self.labels) extra_parameters['labels'] = labels - return super(ProjectIssue, self)._data_for_gitlab(extra_parameters) + return super(ProjectIssue, self)._data_for_gitlab(extra_parameters, + update) def Note(self, id=None, **kwargs): warnings.warn("`Note` is deprecated, use `notes` instead", From 73627a21bc94d6c37adaa36ef3ab0475a05a46f3 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 20:27:41 +0200 Subject: [PATCH 24/85] Add support for project-issue move --- gitlab/cli.py | 11 ++++++++++- gitlab/objects.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index bbd2ac4b2..bc17915bc 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -44,7 +44,9 @@ 'filepath']}, 'builds': {'required': ['id', 'project-id']}}, gitlab.ProjectIssue: {'subscribe': {'required': ['id', 'project-id']}, - 'unsubscribe': {'required': ['id', 'project-id']}}, + 'unsubscribe': {'required': ['id', 'project-id']}, + 'move': {'required': ['id', 'project-id', + 'to-project-id']}}, gitlab.ProjectMergeRequest: { 'closes-issues': {'required': ['id', 'project-id']}, 'cancel': {'required': ['id', 'project-id']}, @@ -280,6 +282,13 @@ def do_project_issue_unsubscribe(self, cls, gl, what, args): except Exception as e: _die("Impossible to subscribe to issue (%s)" % str(e)) + def do_project_issue_move(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.move(args['to_project_id']) + except Exception as e: + _die("Impossible to move issue (%s)" % str(e)) + def do_project_merge_request_closesissues(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) diff --git a/gitlab/objects.py b/gitlab/objects.py index bdadd385e..8638b13dd 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1095,6 +1095,21 @@ def unsubscribe(self, **kwargs): raise_error_from_response(r, GitlabUnsubscribeError) self._set_from_dict(r.json()) + def move(self, to_project_id, **kwargs): + """Move the issue to another project. + + Raises: + GitlabConnectionError: If the server cannot be reached. + """ + url = ('/projects/%(project_id)s/issues/%(issue_id)s/move' % + {'project_id': self.project_id, 'issue_id': self.id}) + + data = {'to_project_id': to_project_id} + data.update(**kwargs) + r = self.gitlab._raw_post(url, data=data) + raise_error_from_response(r, GitlabUpdateError, 201) + self._set_from_dict(r.json()) + class ProjectIssueManager(BaseManager): obj_cls = ProjectIssue From c55fd4b43d6576aff3c679b701c0f5be0cb98281 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 20:36:11 +0200 Subject: [PATCH 25/85] update ProjectLabel attributes --- gitlab/objects.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 8638b13dd..823121916 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1340,9 +1340,9 @@ class ProjectLabel(GitlabObject): idAttr = 'name' requiredDeleteAttrs = ['name'] requiredCreateAttrs = ['name', 'color'] - requiredUpdateAttrs = [] - # FIXME: new_name is only valid with update - optionalCreateAttrs = ['new_name'] + optionalCreateAttrs = ['description'] + requiredUpdateAttrs = ['name'] + optionalUpdateAttrs = ['new_name', 'color', 'description'] class ProjectLabelManager(BaseManager): From dbad3bd007aaa0e4a19a9f3bc87924018f311290 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 21:03:01 +0200 Subject: [PATCH 26/85] milestone: optional listing attrs --- gitlab/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 823121916..2ea875bb9 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1308,6 +1308,7 @@ class ProjectMilestone(GitlabObject): _url = '/projects/%(project_id)s/milestones' canDelete = False requiredUrlAttrs = ['project_id'] + optionalListAttrs = ['iid', 'state'] requiredCreateAttrs = ['title'] optionalCreateAttrs = ['description', 'due_date', 'state_event'] optionalUpdateAttrs = requiredCreateAttrs + optionalCreateAttrs From 79feb87bd98caac008da2337c01fd7e3624d37f6 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 21:15:50 +0200 Subject: [PATCH 27/85] add support for namespaces --- gitlab/__init__.py | 2 ++ gitlab/objects.py | 13 +++++++++++++ tools/python_test.py | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index a8e0fe6a6..836aaeae0 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -73,6 +73,7 @@ class Gitlab(object): hooks (HookManager): Manager for GitLab hooks issues (IssueManager): Manager for GitLab issues licenses (LicenseManager): Manager for licenses + namespaces (NamespaceManager): Manager for namespaces project_branches (ProjectBranchManager): Manager for GitLab projects branches project_commits (ProjectCommitManager): Manager for GitLab projects @@ -143,6 +144,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.hooks = HookManager(self) self.issues = IssueManager(self) self.licenses = LicenseManager(self) + self.namespaces = NamespaceManager(self) self.project_branches = ProjectBranchManager(self) self.project_commits = ProjectCommitManager(self) self.project_commit_comments = ProjectCommitCommentManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 2ea875bb9..96fffdebc 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -782,6 +782,19 @@ class LicenseManager(BaseManager): obj_cls = License +class Namespace(GitlabObject): + _url = '/namespaces' + canGet = 'from_list' + canUpdate = False + canDelete = False + canCreate = False + optionalListAttrs = ['search'] + + +class NamespaceManager(BaseManager): + obj_cls = Namespace + + class ProjectBranch(GitlabObject): _url = '/projects/%(project_id)s/repository/branches' _constructorTypes = {'author': 'User', "committer": "User"} diff --git a/tools/python_test.py b/tools/python_test.py index d09d24b20..c085f622d 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -234,3 +234,9 @@ assert(admin_project.star_count == 1) admin_project = admin_project.unstar() assert(admin_project.star_count == 0) + +# namespaces +ns = gl.namespaces.list() +assert(len(ns) != 0) +ns = gl.namespaces.list(search='root')[0] +assert(ns.kind == 'user') From 6f29ff1727f490e41787a893a0e46c06871041ce Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 20:53:14 +0200 Subject: [PATCH 28/85] Add support for label (un)subscribe --- gitlab/objects.py | 28 ++++++++++++++++++++++++++++ tools/python_test.py | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 823121916..367543b80 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1344,6 +1344,34 @@ class ProjectLabel(GitlabObject): requiredUpdateAttrs = ['name'] optionalUpdateAttrs = ['new_name', 'color', 'description'] + def subscribe(self, **kwargs): + """Subscribe to a label. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabSubscribeError: If the subscription cannot be done + """ + url = ('/projects/%(project_id)s/labels/%(label_id)s/subscription' % + {'project_id': self.project_id, 'label_id': self.name}) + + r = self.gitlab._raw_post(url, **kwargs) + raise_error_from_response(r, GitlabSubscribeError, [201, 304]) + self._set_from_dict(r.json()) + + def unsubscribe(self, **kwargs): + """Unsubscribe a label. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabSubscribeError: If the unsubscription cannot be done + """ + url = ('/projects/%(project_id)s/labels/%(label_id)s/subscription' % + {'project_id': self.project_id, 'label_id': self.name}) + + r = self.gitlab._raw_delete(url, **kwargs) + raise_error_from_response(r, GitlabUnsubscribeError, [200, 304]) + self._set_from_dict(r.json()) + class ProjectLabelManager(BaseManager): obj_cls = ProjectLabel diff --git a/tools/python_test.py b/tools/python_test.py index d09d24b20..90587ac13 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -163,6 +163,10 @@ label1.new_name = 'label1updated' label1.save() assert(label1.name == 'label1updated') +label1.subscribe() +assert(label1.subscribed == True) +label1.unsubscribe() +assert(label1.subscribed == False) label1.delete() # milestones From 867b7abca1cec287a413c9fb190fb5ddd9337cfc Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 19 Jun 2016 22:01:24 +0200 Subject: [PATCH 29/85] MR: add (un)subscribe support --- gitlab/objects.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 51df97cf0..b268e7833 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1099,7 +1099,7 @@ def unsubscribe(self, **kwargs): Raises: GitlabConnectionError: If the server cannot be reached. - GitlabSubscribeError: If the unsubscription cannot be done + GitlabUnsubscribeError: If the unsubscription cannot be done """ url = ('/projects/%(project_id)s/issues/%(issue_id)s/subscription' % {'project_id': self.project_id, 'issue_id': self.id}) @@ -1249,6 +1249,36 @@ def _data_for_gitlab(self, extra_parameters={}, update=False): data = json.dumps(d) return data + def subscribe(self, **kwargs): + """Subscribe to a MR. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabSubscribeError: If the subscription cannot be done + """ + url = ('/projects/%(project_id)s/merge_requests/%(mr_id)s/' + 'subscription' % + {'project_id': self.project_id, 'mr_id': self.id}) + + r = self.gitlab._raw_post(url, **kwargs) + raise_error_from_response(r, GitlabSubscribeError, [201, 304]) + self._set_from_dict(r.json()) + + def unsubscribe(self, **kwargs): + """Unsubscribe a MR. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabUnsubscribeError: If the unsubscription cannot be done + """ + url = ('/projects/%(project_id)s/merge_requests/%(mr_id)s/' + 'subscription' % + {'project_id': self.project_id, 'mr_id': self.id}) + + r = self.gitlab._raw_delete(url, **kwargs) + raise_error_from_response(r, GitlabUnsubscribeError, [200, 304]) + self._set_from_dict(r.json()) + def cancel_merge_when_build_succeeds(self, **kwargs): """Cancel merge when build succeeds.""" @@ -1377,7 +1407,7 @@ def unsubscribe(self, **kwargs): Raises: GitlabConnectionError: If the server cannot be reached. - GitlabSubscribeError: If the unsubscription cannot be done + GitlabUnsubscribeError: If the unsubscription cannot be done """ url = ('/projects/%(project_id)s/labels/%(label_id)s/subscription' % {'project_id': self.project_id, 'label_id': self.name}) From ca662e2d5decbc78a817544227d1efccb5392761 Mon Sep 17 00:00:00 2001 From: Rafael Eyng Date: Sun, 10 Jul 2016 23:28:29 -0300 Subject: [PATCH 30/85] add `note_events` to project hooks attributes --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index b268e7833..9ff823138 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1021,7 +1021,7 @@ class ProjectHook(GitlabObject): _url = '/projects/%(project_id)s/hooks' requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['url'] - optionalCreateAttrs = ['push_events', 'issues_events', + optionalCreateAttrs = ['push_events', 'issues_events', 'note_events', 'merge_requests_events', 'tag_push_events', 'build_events', 'enable_ssl_verification'] shortPrintAttr = 'url' From cdd801ecc6e685ede6db02c9da45b581c07b162e Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 13:18:46 +0200 Subject: [PATCH 31/85] Add branches API documentation --- docs/api-objects.rst | 7 ++++++ docs/gl_objects/branches.py | 33 +++++++++++++++++++++++++++++ docs/gl_objects/branches.rst | 41 ++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 4 files changed, 82 insertions(+) create mode 100644 docs/api-objects.rst create mode 100644 docs/gl_objects/branches.py create mode 100644 docs/gl_objects/branches.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst new file mode 100644 index 000000000..7b5d40b50 --- /dev/null +++ b/docs/api-objects.rst @@ -0,0 +1,7 @@ +######################## +API objects manipulation +######################## + +.. toctree:: + + gl_objects/branches diff --git a/docs/gl_objects/branches.py b/docs/gl_objects/branches.py new file mode 100644 index 000000000..b485ee083 --- /dev/null +++ b/docs/gl_objects/branches.py @@ -0,0 +1,33 @@ +# list +branches = gl.project_branches.list(project_id=1) +# or +branches = project.branches.list() +# end list + +# get +branch = gl.project_branches.get(project_id=1, id='master') +# or +branch = project.branches.get('master') +# end get + +# create +branch = gl.project_branches.create({'branch_name': 'feature1', + 'ref': 'master'}, + project_id=1) +# or +branch = project.branches.create({'branch_name': 'feature1', + 'ref': 'master'}) +# end create + +# delete +gl.project_branches.delete(project_id=1, id='feature1') +# or +project.branches.delete('feature1') +# or +branch.delete() +# end delete + +# protect +branch.protect() +branch.unprotect() +# end protect diff --git a/docs/gl_objects/branches.rst b/docs/gl_objects/branches.rst new file mode 100644 index 000000000..9ec686073 --- /dev/null +++ b/docs/gl_objects/branches.rst @@ -0,0 +1,41 @@ +######## +Branches +######## + +Use :class:`ProjectBranch` objects to manipulate repository branches. + +To create :class:`ProjectBranch` objects use the +:class:`Gitlab.project_branches` or :class:`Project.branches` managers. + +Examples +======== + +Get the list of branches for a repository: + +.. literalinclude:: branches.py + :start-after: # list + :end-before: # end list + +Get a single repository branch: + +.. literalinclude:: branches.py + :start-after: # get + :end-before: # end get + +Create a repository branch: + +.. literalinclude:: branches.py + :start-after: # create + :end-before: # end create + +Delete a repository branch: + +.. literalinclude:: branches.py + :start-after: # delete + :end-before: # end delete + +Protect/unprotect a repository branch: + +.. literalinclude:: branches.py + :start-after: # protect + :end-before: # end protect diff --git a/docs/index.rst b/docs/index.rst index 2fa45da03..f38aea895 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Contents: install cli api-usage + api-objects faq upgrade-from-0.10 api/modules From 9ba2d8957ea2742b4a3f0e00888e544649df1ee3 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 15:09:40 +0200 Subject: [PATCH 32/85] document users API --- docs/api-objects.rst | 1 + docs/gl_objects/users.py | 88 ++++++++++++++++++++++++++ docs/gl_objects/users.rst | 128 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 docs/gl_objects/users.py create mode 100644 docs/gl_objects/users.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 7b5d40b50..8a1516443 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -5,3 +5,4 @@ API objects manipulation .. toctree:: gl_objects/branches + gl_objects/users diff --git a/docs/gl_objects/users.py b/docs/gl_objects/users.py new file mode 100644 index 000000000..99b986f04 --- /dev/null +++ b/docs/gl_objects/users.py @@ -0,0 +1,88 @@ +# list +users = gl.users.list() +# end list + +# search +users = gl.users.list(search='oo') +# end search + +# get +# by ID +user = gl.users.get(2) +# by username +user = gl.users.list(username='root') +# end get + +# create +user = gl.users.create({'email': 'john@doe.com', + 'password': 's3cur3s3cr3T', + 'username': 'jdoe', + 'name': 'John Doe'}) +# end create + +# update +user.name = 'Real Name' +user.save() +# end update + +# delete +gl.users.delete(2) +user.delete() +# end delete + +# block +user.block() +user.unblock() +# end block + +# key list +keys = gl.user_keys.list(user_id=1) +# or +keys = user.keys.list() +# end key list + +# key get +key = gl.user_keys.list(1, user_id=1) +# or +key = user.keys.get(1) +# end key get + +# key create +k = gl.user_keys.create({'title': 'my_key', + 'key': open('/home/me/.ssh/id_rsa.pub').read()}, + user_id=2) +# or +k = user.keys.create({'title': 'my_key', + 'key': open('/home/me/.ssh/id_rsa.pub').read()}) +# end key create + +# key delete +gl.user_keys.delete(1, user_id=1) +# or +user.keys.delete(1) +# or +key.delete() +# end key delete + +# currentuser get +gl.auth() +current_user = gl.user +# end currentuser get + +# currentuser key list +keys = gl.user.keys.list() +# end currentuser key list + +# currentuser key get +key = gl.user.keys.get(1) +# end currentuser key get + +# currentuser key create +key = gl.user.keys.create({'id': 'my_key', 'key': key_content}) +# end currentuser key create + +# currentuser key delete +gl.user.keys.delete(1) +# or +key.delete() +# end currentuser key delete diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst new file mode 100644 index 000000000..dfcfe7302 --- /dev/null +++ b/docs/gl_objects/users.rst @@ -0,0 +1,128 @@ +##### +Users +##### + +Use :class:`User` objects to manipulate repository branches. + +To create :class:`User` objects use the :class:`Gitlab.users` manager. + +Examples +======== + +Get the list of users: + +.. literalinclude:: users.py + :start-after: # list + :end-before: # end list + +Search users whose username match the given string: + +.. literalinclude:: users.py + :start-after: # search + :end-before: # end search + +Get a single user: + +.. literalinclude:: users.py + :start-after: # get + :end-before: # end get + +Create a user: + +.. literalinclude:: users.py + :start-after: # create + :end-before: # end create + +Update a user: + +.. literalinclude:: users.py + :start-after: # update + :end-before: # end update + +Delete a user: + +.. literalinclude:: users.py + :start-after: # delete + :end-before: # end delete + +Block/Unblock a user: + +.. literalinclude:: users.py + :start-after: # block + :end-before: # end block + +SSH keys +======== + +Use the :class:`UserKey` objects to manage user keys. + +To create :class:`UserKey` objects use the :class:`User.keys` or +:class:`Gitlab.user_keys` managers. + +Exemples +-------- + +List SSH keys for a user: + +.. literalinclude:: users.py + :start-after: # key list + :end-before: # end key list + +Get an SSH key for a user: + +.. literalinclude:: users.py + :start-after: # key get + :end-before: # end key get + +Create an SSH key for a user: + +.. literalinclude:: users.py + :start-after: # key create + :end-before: # end key create + +Delete an SSH key for a user: + +.. literalinclude:: users.py + :start-after: # key delete + :end-before: # end key delete + +Current User +============ + +Use the :class:`CurrentUser` object to get information about the currently +logged-in user. + +Examples +-------- + +Get the current user: + +.. literalinclude:: users.py + :start-after: # currentuser get + :end-before: # end currentuser get + +List the current user SSH keys: + +.. literalinclude:: users.py + :start-after: # currentuser key list + :end-before: # end currentuser key list + +Get a key for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser key get + :end-before: # end currentuser key get + +Create a key for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser key create + :end-before: # end currentuser key create + +Delete a key for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser key delete + :end-before: # end currentuser key delete + + From 6f9f42b64cb82929af60e299c70773af6d406a6e Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 15:24:35 +0200 Subject: [PATCH 33/85] docs: crossref improvements --- docs/gl_objects/branches.rst | 8 +++++--- docs/gl_objects/users.rst | 23 ++++++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/gl_objects/branches.rst b/docs/gl_objects/branches.rst index 9ec686073..1b61af34c 100644 --- a/docs/gl_objects/branches.rst +++ b/docs/gl_objects/branches.rst @@ -2,10 +2,12 @@ Branches ######## -Use :class:`ProjectBranch` objects to manipulate repository branches. +Use :class:`~gitlab.objects.ProjectBranch` objects to manipulate repository +branches. -To create :class:`ProjectBranch` objects use the -:class:`Gitlab.project_branches` or :class:`Project.branches` managers. +To create :class:`~gitlab.objects.ProjectBranch` objects use the +:attr:`gitlab.Gitlab.project_branches` or :attr:`Project.branches +` managers. Examples ======== diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index dfcfe7302..d7a9ab746 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -2,9 +2,10 @@ Users ##### -Use :class:`User` objects to manipulate repository branches. +Use :class:`~gitlab.objects.User` objects to manipulate repository branches. -To create :class:`User` objects use the :class:`Gitlab.users` manager. +To create :class:`~gitlab.objects.User` objects use the +:attr:`gitlab.Gitlab.users` manager. Examples ======== @@ -54,10 +55,11 @@ Block/Unblock a user: SSH keys ======== -Use the :class:`UserKey` objects to manage user keys. +Use the :class:`~gitlab.objects.UserKey` objects to manage user keys. -To create :class:`UserKey` objects use the :class:`User.keys` or -:class:`Gitlab.user_keys` managers. +To create :class:`~gitlab.objects.UserKey` objects use the +:attr:`User.keys ` or :attr:`gitlab.Gitlab.user_keys` +managers. Exemples -------- @@ -89,8 +91,13 @@ Delete an SSH key for a user: Current User ============ -Use the :class:`CurrentUser` object to get information about the currently -logged-in user. +Use the :class:`~gitlab.objects.CurrentUser` object to get information about +the currently logged-in user. + +Use the :class:`~gitlab.objects.CurrentUserKey` objects to manage user keys. + +To create :class:`~gitlab.objects.CurrentUserKey` objects use the +:attr:`gitlab.objects.CurrentUser.keys ` manager. Examples -------- @@ -124,5 +131,3 @@ Delete a key for the current user: .. literalinclude:: users.py :start-after: # currentuser key delete :end-before: # end currentuser key delete - - From 0be4761961cf145cf66a456d910596aa32912492 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 16:01:58 +0200 Subject: [PATCH 34/85] Implement user emails support --- docs/gl_objects/users.py | 44 +++++++++++++++++++++++++++ docs/gl_objects/users.rst | 64 +++++++++++++++++++++++++++++++++++++++ gitlab/__init__.py | 2 ++ gitlab/objects.py | 33 ++++++++++++++++++-- tools/python_test.py | 7 +++++ 5 files changed, 148 insertions(+), 2 deletions(-) diff --git a/docs/gl_objects/users.py b/docs/gl_objects/users.py index 99b986f04..9b127a46a 100644 --- a/docs/gl_objects/users.py +++ b/docs/gl_objects/users.py @@ -64,6 +64,32 @@ key.delete() # end key delete +# email list +emails = gl.user_emails.list(user_id=1) +# or +emails = user.emails.list() +# end email list + +# email get +email = gl.user_emails.list(1, user_id=1) +# or +email = user.emails.get(1) +# end email get + +# email create +k = gl.user_emails.create({'email': 'foo@bar.com'}, user_id=2) +# or +k = user.emails.create({'email': 'foo@bar.com'}) +# end email create + +# email delete +gl.user_emails.delete(1, user_id=1) +# or +user.emails.delete(1) +# or +email.delete() +# end email delete + # currentuser get gl.auth() current_user = gl.user @@ -86,3 +112,21 @@ # or key.delete() # end currentuser key delete + +# currentuser email list +emails = gl.user.emails.list() +# end currentuser email list + +# currentuser email get +email = gl.user.emails.get(1) +# end currentuser email get + +# currentuser email create +email = gl.user.emails.create({'email': 'foo@bar.com'}) +# end currentuser email create + +# currentuser email delete +gl.user.emails.delete(1) +# or +email.delete() +# end currentuser email delete diff --git a/docs/gl_objects/users.rst b/docs/gl_objects/users.rst index d7a9ab746..8df93b03f 100644 --- a/docs/gl_objects/users.rst +++ b/docs/gl_objects/users.rst @@ -88,6 +88,41 @@ Delete an SSH key for a user: :start-after: # key delete :end-before: # end key delete +Emails +====== + +Use the :class:`~gitlab.objects.UserEmail` objects to manage user emails. + +To create :class:`~gitlab.objects.UserEmail` objects use the :attr:`User.emails +` or :attr:`gitlab.Gitlab.user_emails` managers. + +Exemples +-------- + +List emails for a user: + +.. literalinclude:: users.py + :start-after: # email list + :end-before: # end email list + +Get an email for a user: + +.. literalinclude:: users.py + :start-after: # email get + :end-before: # end email get + +Create an email for a user: + +.. literalinclude:: users.py + :start-after: # email create + :end-before: # end email create + +Delete an email for a user: + +.. literalinclude:: users.py + :start-after: # email delete + :end-before: # end email delete + Current User ============ @@ -99,6 +134,11 @@ Use the :class:`~gitlab.objects.CurrentUserKey` objects to manage user keys. To create :class:`~gitlab.objects.CurrentUserKey` objects use the :attr:`gitlab.objects.CurrentUser.keys ` manager. +Use the :class:`~gitlab.objects.CurrentUserEmail` objects to manage user emails. + +To create :class:`~gitlab.objects.CurrentUserEmail` objects use the +:attr:`gitlab.objects.CurrentUser.emails ` manager. + Examples -------- @@ -131,3 +171,27 @@ Delete a key for the current user: .. literalinclude:: users.py :start-after: # currentuser key delete :end-before: # end currentuser key delete + +List the current user emails: + +.. literalinclude:: users.py + :start-after: # currentuser email list + :end-before: # end currentuser email list + +Get an email for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser email get + :end-before: # end currentuser email get + +Create an email for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser email create + :end-before: # end currentuser email create + +Delete an email for the current user: + +.. literalinclude:: users.py + :start-after: # currentuser email delete + :end-before: # end currentuser email delete diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 836aaeae0..3ef5dff00 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -65,6 +65,7 @@ class Gitlab(object): http_username: (str): Username for HTTP authentication http_password: (str): Password for HTTP authentication Attributes: + user_emails (UserEmailManager): Manager for GitLab users' emails. user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users group_projects (GroupProjectManager): Manager for GitLab group projects @@ -136,6 +137,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.session = requests.Session() self.settings = ApplicationSettingsManager(self) + self.user_emails = UserEmailManager(self) self.user_keys = UserKeyManager(self) self.users = UserManager(self) self.group_projects = GroupProjectManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 9ff823138..182759596 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -494,6 +494,18 @@ def __ne__(self, other): return not self.__eq__(other) +class UserEmail(GitlabObject): + _url = '/users/%(user_id)s/emails' + canUpdate = False + shortPrintAttr = 'email' + requiredUrlAttrs = ['user_id'] + requiredCreateAttrs = ['email'] + + +class UserEmailManager(BaseManager): + obj_cls = UserEmail + + class UserKey(GitlabObject): _url = '/users/%(user_id)s/keys' canGet = 'from_list' @@ -519,7 +531,10 @@ class User(GitlabObject): 'projects_limit', 'extern_uid', 'provider', 'bio', 'admin', 'can_create_group', 'website_url', 'confirm', 'external'] - managers = [('keys', UserKeyManager, [('user_id', 'id')])] + managers = [ + ('emails', UserEmailManager, [('user_id', 'id')]), + ('keys', UserKeyManager, [('user_id', 'id')]) + ] def _data_for_gitlab(self, extra_parameters={}, update=False): if hasattr(self, 'confirm'): @@ -601,6 +616,17 @@ def get_by_username(self, username, **kwargs): raise GitlabGetError('no such user: ' + username) +class CurrentUserEmail(GitlabObject): + _url = '/user/emails' + canUpdate = False + shortPrintAttr = 'email' + requiredCreateAttrs = ['email'] + + +class CurrentUserEmailManager(BaseManager): + obj_cls = CurrentUserEmail + + class CurrentUserKey(GitlabObject): _url = '/user/keys' canUpdate = False @@ -619,7 +645,10 @@ class CurrentUser(GitlabObject): canUpdate = False canDelete = False shortPrintAttr = 'username' - managers = [('keys', CurrentUserKeyManager, [('user_id', 'id')])] + managers = [ + ('emails', CurrentUserEmailManager, [('user_id', 'id')]), + ('keys', CurrentUserKeyManager, [('user_id', 'id')]) + ] def Key(self, id=None, **kwargs): warnings.warn("`Key` is deprecated, use `keys` instead", diff --git a/tools/python_test.py b/tools/python_test.py index f95b132a2..920486274 100644 --- a/tools/python_test.py +++ b/tools/python_test.py @@ -68,6 +68,13 @@ key = new_user.keys.create({'title': 'testkey', 'key': SSH_KEY}) assert(len(new_user.keys.list()) == 1) key.delete() +assert(len(new_user.keys.list()) == 0) + +# emails +email = new_user.emails.create({'email': 'foo2@bar.com'}) +assert(len(new_user.emails.list()) == 1) +email.delete() +assert(len(new_user.emails.list()) == 0) new_user.delete() foobar_user.delete() From 24578231fabf79659ed5c8277a7a9f2af856cac9 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 17:18:53 +0200 Subject: [PATCH 35/85] Project: add VISIBILITY_* constants They should be there instead of having them in the Group class. Variables in Group are keeped for compatibility. --- gitlab/objects.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 182759596..6f2fac037 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1580,6 +1580,10 @@ class Project(GitlabObject): ('variables', ProjectVariableManager, [('project_id', 'id')]), ] + VISIBILITY_PRIVATE = 0 + VISIBILITY_INTERNAL = 10 + VISIBILITY_PUBLIC = 20 + def Branch(self, id=None, **kwargs): warnings.warn("`Branch` is deprecated, use `branches` instead", DeprecationWarning) From 565c35efe4a4f9c86fba37a7c764ed97788eadd4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 18:14:44 +0200 Subject: [PATCH 36/85] Fix the Project.archive call --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 6f2fac037..7fee47091 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1796,7 +1796,7 @@ def archive(self, sha=None, **kwargs): warnings.warn("`archive` is deprecated, " "use `repository_archive` instead", DeprecationWarning) - return self.repository_archive(path, ref_name, **kwargs) + return self.repository_archive(sha, **kwargs) def repository_archive(self, sha=None, **kwargs): """Return a tarball of the repository. From 7ed34ed79101d0d773ecb6e638b0a4da9c3fd10c Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 18:52:27 +0200 Subject: [PATCH 37/85] Implement archive/unarchive for a projet The methods are called archive_ and unarchive_ to workaround a conflict with the deprecated archive method. Method will be renamed when the archive method is removed. --- gitlab/objects.py | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 7fee47091..8b817717a 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1886,34 +1886,66 @@ def delete_fork_relation(self): r = self.gitlab._raw_delete(url) raise_error_from_response(r, GitlabDeleteError) - def star(self): + def star(self, **kwargs): """Star a project. Returns: Project: the updated Project Raises: + GitlabCreateError: If the action cannot be done GitlabConnectionError: If the server cannot be reached. """ url = "/projects/%s/star" % self.id - r = self.gitlab._raw_post(url) - raise_error_from_response(r, GitlabGetError, [201, 304]) + r = self.gitlab._raw_post(url, **kwargs) + raise_error_from_response(r, GitlabCreateError, [201, 304]) return Project(self.gitlab, r.json()) if r.status_code == 201 else self - def unstar(self): + def unstar(self, **kwargs): """Unstar a project. Returns: Project: the updated Project Raises: + GitlabDeleteError: If the action cannot be done GitlabConnectionError: If the server cannot be reached. """ url = "/projects/%s/star" % self.id - r = self.gitlab._raw_delete(url) + r = self.gitlab._raw_delete(url, **kwargs) raise_error_from_response(r, GitlabDeleteError, [200, 304]) return Project(self.gitlab, r.json()) if r.status_code == 200 else self + def archive_(self, **kwargs): + """Archive a project. + + Returns: + Project: the updated Project + + Raises: + GitlabCreateError: If the action cannot be done + GitlabConnectionError: If the server cannot be reached. + """ + url = "/projects/%s/archive" % self.id + r = self.gitlab._raw_post(url, **kwargs) + raise_error_from_response(r, GitlabCreateError, 201) + return Project(self.gitlab, r.json()) if r.status_code == 201 else self + + def unarchive_(self, **kwargs): + """Unarchive a project. + + Returns: + Project: the updated Project + + Raises: + GitlabDeleteError: If the action cannot be done + GitlabConnectionError: If the server cannot be reached. + """ + url = "/projects/%s/unarchive" % self.id + r = self.gitlab._raw_delete(url, **kwargs) + raise_error_from_response(r, GitlabCreateError, 201) + return Project(self.gitlab, r.json()) if r.status_code == 201 else self + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' From 967595f504b8de076ae9218a96c3b8dd6273b9d6 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 18:55:59 +0200 Subject: [PATCH 38/85] docs: document projects API --- docs/api-objects.rst | 1 + docs/gl_objects/projects.py | 67 +++++++++++++++++++++++++ docs/gl_objects/projects.rst | 96 ++++++++++++++++++++++++++++++++++++ docs/gl_objects/users.py | 2 +- 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 docs/gl_objects/projects.py create mode 100644 docs/gl_objects/projects.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 8a1516443..19e8a22c8 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -5,4 +5,5 @@ API objects manipulation .. toctree:: gl_objects/branches + gl_objects/projects gl_objects/users diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py new file mode 100644 index 000000000..24ce6d6cc --- /dev/null +++ b/docs/gl_objects/projects.py @@ -0,0 +1,67 @@ +# list +# Active projects +projects = gl.projects.list() +# Archived projects +projects = gl.projects.list(archived=1) +# Limit to projects with a defined visibility +projects = gl.projects.list(visibility='public') + +# List owned projects +projects = gl.projects.owned() + +# List starred projects +projects = gl.projects.starred() + +# List all the projects +projects = gl.projects.all() +# end list + +# get +# Get a project by ID +project = gl.projects.get(10) +# Get a project by userspace/name +project = gl.projects.get('myteam/myproject') +# end get + +# create +project = gl.projects.create({'name': 'project1'}) +# end create + +# user create +alice gl.users.list(username='alice')[0] +user_project = gl.user_projects.create({'name': 'project', + 'user_id': alice.id}) +# end user create + +# update +project.snippets_enabled = 1 +project.save() +# end update + +# delete +gl.projects.delete(1) +# or +project.delete() +# end delete + +# fork +fork = gl.project_forks.create(project_id=1) +# or +fork = project.fork() +# end fork + +# star +p.star() +p.unstar() +# end star + +# archive +p.archive_() +p.unarchive_() +# end archive + +# events list +gl.project_events.list(project_id=1) +# or +project.events.list() +# end events list diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst new file mode 100644 index 000000000..f800499f9 --- /dev/null +++ b/docs/gl_objects/projects.rst @@ -0,0 +1,96 @@ +######## +Projects +######## + +Use :class:`~gitlab.objects.Project` objects to manipulate projects. The +:attr:`gitlab.Gitlab.projects` manager objects provides helper functions. + +Examples +======== + +List projects: + +The API provides several filtering parameters for the listing methods: + +* ``archived``: if ``True`` only archived projects will be returned +* ``visibility``: returns only projects with the specified visibility (can be + ``public``, ``internal`` or ``private``) +* ``search``: returns project matching the given pattern + +Results can also be sorted using the following parameters: + +* ``order_by``: sort using the given argument. Valid values are ``id``, + ``name``, ``path``, ``created_at``, ``updated_at`` and ``last_activity_at``. + The default is to sort by ``created_at`` +* ``sort``: sort order (``asc`` or ``desc``) + +.. literalinclude:: projects.py + :start-after: # list + :end-before: # end list + +Get a single project: + +.. literalinclude:: projects.py + :start-after: # get + :end-before: # end get + +Create a project: + +.. literalinclude:: projects.py + :start-after: # create + :end-before: # end create + +Create a project for a user (admin only): + +.. literalinclude:: projects.py + :start-after: # user create + :end-before: # end user create + +Update a project: + +.. literalinclude:: projects.py + :start-after: # update + :end-before: # end update + +Delete a project: + +.. literalinclude:: projects.py + :start-after: # delete + :end-before: # end delete + +Fork a project : + +.. literalinclude:: projects.py + :start-after: # fork + :end-before: # end fork + +Star/unstar a project: + +.. literalinclude:: projects.py + :start-after: # star + :end-before: # end star + +Archive/unarchive a project: + +.. literalinclude:: projects.py + :start-after: # archive + :end-before: # end archive + +.. note:: + + The underscore character at the end of the methods is used to workaround a + conflict with a previous misuse of the ``archive`` method (deprecated but + not yet removed). + +Events +------ + +Use :class:`~gitlab.objects.ProjectEvent` objects to manipulate projects. The +:attr:`gitlab.Gitlab.project_events` and :attr:`Project.events +` manager objects provide helper functions. + +List the project events: + +.. literalinclude:: projects.py + :start-after: # events list + :end-before: # end events list diff --git a/docs/gl_objects/users.py b/docs/gl_objects/users.py index 9b127a46a..798678d13 100644 --- a/docs/gl_objects/users.py +++ b/docs/gl_objects/users.py @@ -10,7 +10,7 @@ # by ID user = gl.users.get(2) # by username -user = gl.users.list(username='root') +user = gl.users.list(username='root')[0] # end get # create From 3ad612de8753e20f7359c16e5bce4c06772c9550 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 18:59:00 +0200 Subject: [PATCH 39/85] Update ProjectSnippet attributes 'visibility_level' has been added as an optional attribute to keep compatibility with older releases of gitlab. Fixes #129 --- gitlab/objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 8b817717a..08ca3c11a 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1494,7 +1494,8 @@ class ProjectSnippet(GitlabObject): _constructorTypes = {'author': 'User'} requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['title', 'file_name', 'code'] - optionalCreateAttrs = ['lifetime'] + optionalCreateAttrs = ['lifetime', 'visibility_level'] + optionalUpdateAttrs = ['title', 'file_name', 'code', 'visibility_level'] shortPrintAttr = 'title' managers = [('notes', ProjectSnippetNoteManager, [('project_id', 'project_id'), ('snippet_id', 'id')])] From 2df4c9e52a89de7256dacef9cb567ea1b2e056f4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 19:25:19 +0200 Subject: [PATCH 40/85] Fix ProjectMember update --- gitlab/objects.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 08ca3c11a..44f34cd25 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1161,6 +1161,7 @@ class ProjectMember(GitlabObject): _url = '/projects/%(project_id)s/members' requiredUrlAttrs = ['project_id'] requiredCreateAttrs = ['access_level', 'user_id'] + requiredUpdateAttrs = ['access_level'] shortPrintAttr = 'username' From dcf31a425217efebe56d4cbc8250dceb3844b2fa Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 19:28:25 +0200 Subject: [PATCH 41/85] docs: add project members doc --- docs/gl_objects/projects.py | 40 +++++++++++++++++++++++++++++++++ docs/gl_objects/projects.rst | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 24ce6d6cc..cd1367563 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -65,3 +65,43 @@ # or project.events.list() # end events list + +# members list +members = gl.project_members.list() +# or +members = project.members.list() +# end members list + +# members search +members = gl.project_members.list(query='foo') +# or +members = project.members.list(query='bar') +# end members search + +# members get +member = gl.project_members.get(1) +# or +member = project.members.get(1) +# end members get + +# members add +member = gl.project_members.create({'user_id': user.id, 'access_level': + gitlab.Group.DEVELOPER_ACCESS}, + project_id=1) +# or +member = project.members.create({'user_id': user.id, 'access_level': + gitlab.Group.DEVELOPER_ACCESS}) +# end members add + +# members update +member.access_level = gitlab.Group.MASTER_ACCESS +member.save() +# end members update + +# members delete +gl.project_members.delete(user.id, project_id=1) +# or +project.members.delete(user.id) +# or +member.delete() +# end members delete diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index f800499f9..3bdeff446 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -94,3 +94,46 @@ List the project events: .. literalinclude:: projects.py :start-after: # events list :end-before: # end events list + +Team members +------------ + +Use :class:`~gitlab.objects.ProjectMember` objects to manipulate projects +members. The :attr:`gitlab.Gitlab.project_members` and :attr:`Project.members +` manager objects provide helper functions. + +List the project members: + +.. literalinclude:: projects.py + :start-after: # members list + :end-before: # end members list + +Search project members matching a query string: + +.. literalinclude:: projects.py + :start-after: # members search + :end-before: # end members search + +Get a single project member: + +.. literalinclude:: projects.py + :start-after: # members get + :end-before: # end members get + +Add a project member: + +.. literalinclude:: projects.py + :start-after: # members add + :end-before: # end members add + +Modify a project member (change the access level): + +.. literalinclude:: projects.py + :start-after: # members update + :end-before: # end members update + +Remove a member from the project team: + +.. literalinclude:: projects.py + :start-after: # members delete + :end-before: # end members delete From e7c412510ba014f6e1cf94b725b0e24665c7b4ed Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 19:46:46 +0200 Subject: [PATCH 42/85] Implement sharing project with a group --- docs/gl_objects/projects.py | 4 ++++ docs/gl_objects/projects.rst | 6 ++++++ gitlab/objects.py | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index cd1367563..d03f0d315 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -105,3 +105,7 @@ # or member.delete() # end members delete + +# share +project.share(group.id, group.DEVELOPER_ACCESS) +# end share diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 3bdeff446..bd56fbaef 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -137,3 +137,9 @@ Remove a member from the project team: .. literalinclude:: projects.py :start-after: # members delete :end-before: # end members delete + +Share the project with a group: + +.. literalinclude:: projects.py + :start-after: # share + :end-before: # end share diff --git a/gitlab/objects.py b/gitlab/objects.py index 44f34cd25..dba54f0a1 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1948,6 +1948,22 @@ def unarchive_(self, **kwargs): raise_error_from_response(r, GitlabCreateError, 201) return Project(self.gitlab, r.json()) if r.status_code == 201 else self + def share(self, group_id, group_access, **kwargs): + """Share the project with a group. + + Args: + group_id (int): ID of the group. + group_access (int): Access level for the group. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabCreateError: If the server fails to perform the request. + """ + url = "/projects/%s/share" % self.id + data = {'group_id': group_id, 'group_access': group_access} + r = self.gitlab._raw_post(url, data=data, **kwargs) + raise_error_from_response(r, GitlabCreateError, 201) + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' From fc68e9bbf8cf24eca4faf57997f0c7273944eabe Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 19:56:22 +0200 Subject: [PATCH 43/85] implement CLI for project archive/unarchive/share --- gitlab/cli.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index bc17915bc..e8bb42524 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -61,7 +61,10 @@ 'all': {}, 'starred': {}, 'star': {'required': ['id']}, - 'unstar': {'required': ['id']}}, + 'unstar': {'required': ['id']}, + 'archive': {'required': ['id']}, + 'unarchive': {'required': ['id']}, + 'share': {'required': ['id', 'group-id', 'group-access']}}, gitlab.User: {'block': {'required': ['id']}, 'unblock': {'required': ['id']}, 'search': {'required': ['query']}, @@ -205,6 +208,27 @@ def do_project_unstar(self, cls, gl, what, args): except Exception as e: _die("Impossible to unstar project (%s)" % str(e)) + def do_project_archive(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.archive_() + except Exception as e: + _die("Impossible to archive project (%s)" % str(e)) + + def do_project_unarchive(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.unarchive_() + except Exception as e: + _die("Impossible to unarchive project (%s)" % str(e)) + + def do_project_share(self, cls, gl, what, args): + try: + o = self.do_get(cls, gl, what, args) + o.share(args['group_id'], args['group_access']) + except Exception as e: + _die("Impossible to share project (%s)" % str(e)) + def do_user_block(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) From 63f0c4d21e14b06e8a70e9b752262399e2195b31 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 16 Jul 2016 20:05:46 +0200 Subject: [PATCH 44/85] Fix pep8 test --- gitlab/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index e8bb42524..8739854d1 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -64,7 +64,8 @@ 'unstar': {'required': ['id']}, 'archive': {'required': ['id']}, 'unarchive': {'required': ['id']}, - 'share': {'required': ['id', 'group-id', 'group-access']}}, + 'share': {'required': ['id', 'group-id', + 'group-access']}}, gitlab.User: {'block': {'required': ['id']}, 'unblock': {'required': ['id']}, 'search': {'required': ['query']}, From b21dca0acb2c12add229a1742e0c552aa50618c1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 07:41:48 +0200 Subject: [PATCH 45/85] docs: document hooks API --- docs/gl_objects/projects.py | 33 ++++++++++++++++++++++++++++++++ docs/gl_objects/projects.rst | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index d03f0d315..112e27bd0 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -109,3 +109,36 @@ # share project.share(group.id, group.DEVELOPER_ACCESS) # end share + +# hook list +hooks = gl.project_hooks.list(project_id=1) +# or +hooks = project.hooks.list() +# end hook list + +# hook get +hook = gl.project_hooks.get(1, project_id=1) +# or +hook = project.hooks.get(1) +# end hook get + +# hook create +hook = gl.project_hooks.create({'url': 'http://my/action/url', + 'push_events': 1}, + project_id=1) +# or +hook = project.hooks.create({'url': 'http://my/action/url', 'push_events': 1}) +# end hook create + +# hook update +hook.push_events = 0 +hook.save() +# end hook update + +# hook delete +gl.project_hooks.delete(1, project_id=1) +# or +project.hooks.delete(1) +# or +hook.delete() +# end hook delete diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index bd56fbaef..d0c0d27d3 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -143,3 +143,40 @@ Share the project with a group: .. literalinclude:: projects.py :start-after: # share :end-before: # end share + +Hooks +----- + +Use :class:`~gitlab.objects.ProjectHook` objects to manipulate projects +hooks. The :attr:`gitlab.Gitlab.project_hooks` and :attr:`Project.hooks +` manager objects provide helper functions. + +List the project hooks: + +.. literalinclude:: projects.py + :start-after: # hook list + :end-before: # end hook list + +Get a project hook + +.. literalinclude:: projects.py + :start-after: # hook get + :end-before: # end hook get + +Create a project hook: + +.. literalinclude:: projects.py + :start-after: # hook create + :end-before: # end hook create + +Update a project hook: + +.. literalinclude:: projects.py + :start-after: # hook update + :end-before: # end hook update + +Delete a project hook: + +.. literalinclude:: projects.py + :start-after: # hook delete + :end-before: # end hook delete From e4cd04c225e2160f02a8f292dbd4c0f6350769e4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 07:43:59 +0200 Subject: [PATCH 46/85] docs: project search API --- docs/gl_objects/projects.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 112e27bd0..8cf0e3c9f 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -14,6 +14,9 @@ # List all the projects projects = gl.projects.all() + +# Search projects +projects = gl.projects.search('query') # end list # get From 21f48b357130720551d5cccbc62f5275fe970378 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 07:49:11 +0200 Subject: [PATCH 47/85] docs: fork relationship API --- docs/gl_objects/projects.py | 13 +++++++++---- docs/gl_objects/projects.rst | 8 +++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 8cf0e3c9f..f37cf9fae 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -53,14 +53,19 @@ fork = project.fork() # end fork +# forkrelation +project.create_fork_relation(source_project.id) +project.delete_fork_relation() +# end forkrelation + # star -p.star() -p.unstar() +project.star() +project.unstar() # end star # archive -p.archive_() -p.unarchive_() +project.archive_() +project.unarchive_() # end archive # events list diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index d0c0d27d3..e9c5b6cea 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -58,12 +58,18 @@ Delete a project: :start-after: # delete :end-before: # end delete -Fork a project : +Fork a project: .. literalinclude:: projects.py :start-after: # fork :end-before: # end fork +Create/delete a fork relation between projects (requires admin permissions): + +.. literalinclude:: projects.py + :start-after: # forkrelation + :end-before: # end forkrelation + Star/unstar a project: .. literalinclude:: projects.py From 52c8825f7db7ff9a0a24a2bda6451af747822589 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 08:32:15 +0200 Subject: [PATCH 48/85] Add docstring for settings manager in Gitlab class --- gitlab/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 3ef5dff00..3875cc821 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -109,6 +109,7 @@ class Gitlab(object): snippets user_projects (UserProjectManager): Manager for GitLab projects users projects (ProjectManager): Manager for GitLab projects + settings (ApplicationSettingsManager): manager for the Gitlab settings team_members (TeamMemberManager): Manager for GitLab teams members team_projects (TeamProjectManager): Manager for GitLab teams projects teams (TeamManager): Manager for GitLab teams From 6eb11fd73840889a7ab244c516c235a2dc7e6867 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 09:16:34 +0200 Subject: [PATCH 49/85] Implement runners global API --- docs/api-objects.rst | 1 + docs/gl_objects/runners.py | 22 ++++++++++++++++++ docs/gl_objects/runners.rst | 45 +++++++++++++++++++++++++++++++++++++ gitlab/__init__.py | 2 ++ gitlab/objects.py | 29 ++++++++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 docs/gl_objects/runners.py create mode 100644 docs/gl_objects/runners.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 19e8a22c8..2f9263ad5 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -6,4 +6,5 @@ API objects manipulation gl_objects/branches gl_objects/projects + gl_objects/runners gl_objects/users diff --git a/docs/gl_objects/runners.py b/docs/gl_objects/runners.py new file mode 100644 index 000000000..5092dc08f --- /dev/null +++ b/docs/gl_objects/runners.py @@ -0,0 +1,22 @@ +# list +# List owned runners +runners = gl.runners.list() +# List all runners, using a filter +runners = gl.runners.all(scope='paused') +# end list + +# get +runner = gl.runners.get(runner_id) +# end get + +# update +runner = gl.runners.get(runner_id) +runner.tag_list.append('new_tag') +runner.save() +# end update + +# delete +gl.runners.delete(runner_id) +# or +runner.delete() +# end delete diff --git a/docs/gl_objects/runners.rst b/docs/gl_objects/runners.rst new file mode 100644 index 000000000..08c4bc719 --- /dev/null +++ b/docs/gl_objects/runners.rst @@ -0,0 +1,45 @@ +####### +Runners +####### + +Global runners +============== + +Use :class:`~gitlab.objects.Runner` objects to manipulate runners. The +:attr:`gitlab.Gitlab.runners` manager object provides helper functions. + +Examples +-------- + +Use the ``list()`` and ``all()`` methods to list runners. + + The ``all()`` method accepts a ``scope`` parameter to filter the list. Allowed +values for this parameter are ``specific``, ``shared``, ``active``, ``paused`` +and ``online``. + +.. note:: + + The returned objects hold minimal information about the runners. Use the + ``get()`` method to retrieve detail about a runner. + +.. literalinclude:: runners.py + :start-after: # list + :end-before: # end list + +Get a runner's detail: + +.. literalinclude:: runners.py + :start-after: # get + :end-before: # end get + +Update a runner: + +.. literalinclude:: runners.py + :start-after: # update + :end-before: # end update + +Remove a runner: + +.. literalinclude:: runners.py + :start-after: # delete + :end-before: # end delete diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 3875cc821..41ba4df51 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -109,6 +109,7 @@ class Gitlab(object): snippets user_projects (UserProjectManager): Manager for GitLab projects users projects (ProjectManager): Manager for GitLab projects + runners (RunnerManager): Manager for the CI runners settings (ApplicationSettingsManager): manager for the Gitlab settings team_members (TeamMemberManager): Manager for GitLab teams members team_projects (TeamProjectManager): Manager for GitLab teams projects @@ -169,6 +170,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.project_snippets = ProjectSnippetManager(self) self.user_projects = UserProjectManager(self) self.projects = ProjectManager(self) + self.runners = RunnerManager(self) self.team_members = TeamMemberManager(self) self.team_projects = TeamProjectManager(self) self.teams = TeamManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index dba54f0a1..a760a16d3 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1965,6 +1965,35 @@ def share(self, group_id, group_access, **kwargs): raise_error_from_response(r, GitlabCreateError, 201) +class Runner(GitlabObject): + _url = '/runners' + canCreate = False + optionalUpdateAttrs = ['description', 'active', 'tag_list'] + + +class RunnerManager(BaseManager): + obj_cls = Runner + + def all(self, scope=None, **kwargs): + """List all the runners. + + Args: + scope (str): The scope of runners to show, one of: specific, + shared, active, paused, online + + Returns: + list(Runner): a list of runners matching the scope. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabListError; If the resource cannot be found + """ + url = '/runners/all' + if scope is not None: + url += '?scope=' + scope + return self.gitlab._raw_list(url, self.obj_cls, **kwargs) + + class TeamMember(GitlabObject): _url = '/user_teams/%(team_id)s/members' canUpdate = False From b339ed98ee4981dd494096f73e5e8a8eb0b6116b Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 12:33:55 +0200 Subject: [PATCH 50/85] Gitlab: add managers for build-related resources --- gitlab/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 41ba4df51..6aa9c1850 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -77,6 +77,8 @@ class Gitlab(object): namespaces (NamespaceManager): Manager for namespaces project_branches (ProjectBranchManager): Manager for GitLab projects branches + project_builds (ProjectBuildManager): Manager for GitLab projects + builds project_commits (ProjectCommitManager): Manager for GitLab projects commits project_commitcomments (ProjectCommitCommentManager): Manager for @@ -107,6 +109,8 @@ class Gitlab(object): note on snippets project_snippets (ProjectSnippetManager): Manager for GitLab projects snippets + project_triggers (ProjectTriggerManager): Manager for build triggers + project_variables (ProjectVariableManager): Manager for build variables user_projects (UserProjectManager): Manager for GitLab projects users projects (ProjectManager): Manager for GitLab projects runners (RunnerManager): Manager for the CI runners @@ -150,6 +154,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.licenses = LicenseManager(self) self.namespaces = NamespaceManager(self) self.project_branches = ProjectBranchManager(self) + self.project_builds = ProjectBuildManager(self) self.project_commits = ProjectCommitManager(self) self.project_commit_comments = ProjectCommitCommentManager(self) self.project_keys = ProjectKeyManager(self) @@ -168,6 +173,8 @@ def __init__(self, url, private_token=None, email=None, password=None, self.project_files = ProjectFileManager(self) self.project_snippet_notes = ProjectSnippetNoteManager(self) self.project_snippets = ProjectSnippetManager(self) + self.project_triggers = ProjectTriggerManager(self) + self.project_variables = ProjectVariableManager(self) self.user_projects = UserProjectManager(self) self.projects = ProjectManager(self) self.runners = RunnerManager(self) From e0cf1c276d16ba9a0e26853e5ac94668a5b60818 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 13:11:48 +0200 Subject: [PATCH 51/85] Implement ProjectBuild.keep_artifacts --- gitlab/objects.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index a760a16d3..7d2b87973 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -858,7 +858,8 @@ class ProjectBranchManager(BaseManager): class ProjectBuild(GitlabObject): _url = '/projects/%(project_id)s/builds' _constructorTypes = {'user': 'User', - 'commit': 'ProjectCommit'} + 'commit': 'ProjectCommit', + 'runner': 'Runner'} requiredUrlAttrs = ['project_id'] canDelete = False canUpdate = False @@ -876,6 +877,18 @@ def retry(self, **kwargs): r = self.gitlab._raw_post(url) raise_error_from_response(r, GitlabBuildRetryError, 201) + def keep_artifacts(self, **kwargs): + """Prevent artifacts from being delete when expiration is set. + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabCreateError: If the request failed. + """ + url = ('/projects/%s/builds/%s/artifacts/keep' % + (self.project_id, self.id)) + r = self.gitlab._raw_post(url) + raise_error_from_response(r, GitlabGetError, 200) + def artifacts(self, **kwargs): """Get the build artifacts. From 8e6a9442324926ed1dec0a8bfaf77792e4bdb10f Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 13:15:31 +0200 Subject: [PATCH 52/85] docs: Add builds-related API docs --- docs/api-objects.rst | 1 + docs/gl_objects/builds.py | 99 +++++++++++++++++++++++++ docs/gl_objects/builds.rst | 147 +++++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 docs/gl_objects/builds.py create mode 100644 docs/gl_objects/builds.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 2f9263ad5..1212fb68f 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -5,6 +5,7 @@ API objects manipulation .. toctree:: gl_objects/branches + gl_objects/builds gl_objects/projects gl_objects/runners gl_objects/users diff --git a/docs/gl_objects/builds.py b/docs/gl_objects/builds.py new file mode 100644 index 000000000..4b663c6e3 --- /dev/null +++ b/docs/gl_objects/builds.py @@ -0,0 +1,99 @@ +# var list +variables = gl.project_variables.list(project_id=1) +# or +variables = project.variables.list() +# end var list + +# var get +var = gl.project_variables.get(var_key, project_id=1) +# or +var = project.variables.get(var_key) +# end var get + +# var create +var = gl.project_variables.create({'key': 'key1', 'value': 'value1'}, + project_id=1) +# or +var = project.variables.create({'key': 'key1', 'value': 'value1'}) +# end var create + +# var update +var.value = 'new_value' +var.save() +# end var update + +# var delete +gl.project_variables.delete(var_key) +# or +project.variables.delete() +# or +var.delete() +# end var delete + +# trigger list +triggers = gl.project_triggers.list(project_id=1) +# or +triggers = project.triggers.list() +# end trigger list + +# trigger get +trigger = gl.project_triggers.get(trigger_token, project_id=1) +# or +trigger = project.triggers.get(trigger_token) +# end trigger get + +# trigger create +trigger = gl.project_triggers.create({}, project_id=1) +# or +trigger = project.triggers.create({}) +# end trigger create + +# trigger delete +gl.project_triggers.delete(trigger_token) +# or +project.triggers.delete() +# or +trigger.delete() +# end trigger delete + +# list +builds = gl.project_builds.list(project_id=1) +# or +builds = project.builds.list() +# end list + +# commit list +commit = gl.project_commits.get(commit_sha, project_id=1) +builds = commit.builds() +# end commit list + +# get +build = gl.project_builds.get(build_id, project_id=1) +# or +project.builds.get(build_id) +# end get + +# artifacts +build.artifacts() +# end artifacts + +# keep artifacts +build.keep_artifacts() +# end keep artifacts + +# trace +build.trace() +# end trace + +# retry +build.cancel() +build.retry() +# end retry + +# delete +gl.project_builds.delete(build_id, project_id=1) +# or +project.builds.delete(build_id) +# or +build.delete() +# end delete diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst new file mode 100644 index 000000000..23f47f0f4 --- /dev/null +++ b/docs/gl_objects/builds.rst @@ -0,0 +1,147 @@ +###### +Builds +###### + +Build triggers +============== + +Use :class:`~gitlab.objects.ProjectTrigger` objects to manipulate build +triggers. The :attr:`gitlab.Gitlab.project_triggers` and +:attr:`gitlab.objects.Projeect.triggers` manager objects provide helper +functions. + +Examples +-------- + +List triggers: + +.. literalinclude:: builds.py + :start-after: # trigger list + :end-before: # end trigger list + +Get a trigger: + +.. literalinclude:: builds.py + :start-after: # trigger get + :end-before: # end trigger get + +Create a trigger: + +.. literalinclude:: builds.py + :start-after: # trigger create + :end-before: # end trigger create + +Remove a trigger: + +.. literalinclude:: builds.py + :start-after: # trigger delete + :end-before: # end trigger delete + +Build variables +=============== + +Use :class:`~gitlab.objects.ProjectVariable` objects to manipulate build +variables. The :attr:`gitlab.Gitlab.project_variables` and +:attr:`gitlab.objects.Projeect.variables` manager objects provide helper +functions. + +Examples +-------- + +List variables: + +.. literalinclude:: builds.py + :start-after: # var list + :end-before: # end var list + +Get a variable: + +.. literalinclude:: builds.py + :start-after: # var get + :end-before: # end var get + +Create a variable: + +.. literalinclude:: builds.py + :start-after: # var create + :end-before: # end var create + +Update a variable value: + +.. literalinclude:: builds.py + :start-after: # var update + :end-before: # end var update + +Remove a variable: + +.. literalinclude:: builds.py + :start-after: # var delete + :end-before: # end var delete + +Builds +====== + +Use :class:`~gitlab.objects.ProjectBuild` objects to manipulate builds. The +:attr:`gitlab.Gitlab.project_builds` and :attr:`gitlab.objects.Projeect.builds` +manager objects provide helper functions. + +Examples +-------- + +List builds for the project: + +.. literalinclude:: builds.py + :start-after: # list + :end-before: # end list + +To list builds for a specific commit, create a +:class:`~gitlab.objects.ProjectCommit` object and use its +:attr:`~gitlab.objects.ProjectCommit.builds` method: + +.. literalinclude:: builds.py + :start-after: # commit list + :end-before: # end commit list + +Get a build: + +.. literalinclude:: builds.py + :start-after: # get + :end-before: # end get + +Get a build artifacts: + +.. literalinclude:: builds.py + :start-after: # artifacts + :end-before: # end artifacts + +.. warning:: + + Artifacts are entirely stored in memory. + +Mark a build artifact as kept when expiration is set: + +.. literalinclude:: builds.py + :start-after: # keep artifacts + :end-before: # end keep artifacts + +Get a build trace: + +.. literalinclude:: builds.py + :start-after: # trace + :end-before: # end trace + +.. warning:: + + Traces are entirely stored in memory. + +Cancel/retry a build: + +.. literalinclude:: builds.py + :start-after: # retry + :end-before: # end retry + +Erase a build: + +.. literalinclude:: builds.py + :start-after: # delete + :end-before: # end delete From 94aea524a23ac428259bae327a1fccdd2f5b841d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 14:09:39 +0200 Subject: [PATCH 53/85] Allow to stream the downloads when appropriate Some API calls will download possibly large data, resulting in a high memory usage and out-of-memory errors. For these API calls use the requests streaming capabilities and download chunked data. The caller is responsible of providing a callable to actually store the data. The default callable just prints the data on stdout. --- docs/gl_objects/builds.py | 13 ++++++++++ docs/gl_objects/builds.rst | 14 ++++++++-- gitlab/__init__.py | 3 ++- gitlab/objects.py | 53 ++++++++++++++++++++++++-------------- gitlab/utils.py | 15 +++++++++++ 5 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 gitlab/utils.py diff --git a/docs/gl_objects/builds.py b/docs/gl_objects/builds.py index 4b663c6e3..c535cb294 100644 --- a/docs/gl_objects/builds.py +++ b/docs/gl_objects/builds.py @@ -77,6 +77,19 @@ build.artifacts() # end artifacts +# stream artifacts +class Foo(object): + def __init__(self): + self._fd = open('artifacts.zip', 'w') + + def __call__(self, chunk): + self._fd.write(chunk) + +target = Foo() +build.artifacts(streamed=True, streamed=True, action=target) +del(target) # flushes data on disk +# end stream artifacts + # keep artifacts build.keep_artifacts() # end keep artifacts diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index 23f47f0f4..ce4cc0e53 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -116,7 +116,16 @@ Get a build artifacts: .. warning:: - Artifacts are entirely stored in memory. + Artifacts are entirely stored in memory in this example. + +.. _streaming_example: + +You can download artifacts as a stream. Provide a callable to handle the +stream: + +.. literalinclude:: builds.py + :start-after: # stream artifacts + :end-before: # end stream artifacts Mark a build artifact as kept when expiration is set: @@ -132,7 +141,8 @@ Get a build trace: .. warning:: - Traces are entirely stored in memory. + Traces are entirely stored in memory unless you use the streaming feature. + See :ref:`the artifacts example `. Cancel/retry a build: diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 6aa9c1850..d702f3101 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -286,7 +286,7 @@ def set_credentials(self, email, password): self.email = email self.password = password - def _raw_get(self, path, content_type=None, **kwargs): + def _raw_get(self, path, content_type=None, streamed=False, **kwargs): url = '%s%s' % (self._url, path) headers = self._create_headers(content_type) try: @@ -295,6 +295,7 @@ def _raw_get(self, path, content_type=None, **kwargs): headers=headers, verify=self.ssl_verify, timeout=self.timeout, + stream=streamed, auth=requests.auth.HTTPBasicAuth( self.http_username, self.http_password)) diff --git a/gitlab/objects.py b/gitlab/objects.py index 7d2b87973..a8b92de5e 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -29,6 +29,7 @@ import gitlab from gitlab.exceptions import * # noqa +from gitlab import utils class jsonEncoder(json.JSONEncoder): @@ -889,22 +890,31 @@ def keep_artifacts(self, **kwargs): r = self.gitlab._raw_post(url) raise_error_from_response(r, GitlabGetError, 200) - def artifacts(self, **kwargs): + def artifacts(self, streamed=False, action=None, chunk_size=1024, + **kwargs): """Get the build artifacts. + Args: + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. + Returns: - str: The artifacts. + str: The artifacts if `streamed` is False, None otherwise. Raises: GitlabConnectionError: If the server cannot be reached. GitlabGetError: If the artifacts are not available. """ url = '/projects/%s/builds/%s/artifacts' % (self.project_id, self.id) - r = self.gitlab._raw_get(url) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError, 200) - return r.content + return utils.response_content(r, streamed, action, chunk_size) - def trace(self, **kwargs): + def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Get the build trace. Returns: @@ -915,9 +925,9 @@ def trace(self, **kwargs): GitlabGetError: If the trace is not available. """ url = '/projects/%s/builds/%s/trace' % (self.project_id, self.id) - r = self.gitlab._raw_get(url) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError, 200) - return r.content + return utils.response_content(r, streamed, action, chunk_size) class ProjectBuildManager(BaseManager): @@ -972,7 +982,8 @@ def diff(self, **kwargs): return r.json() - def blob(self, filepath, **kwargs): + def blob(self, filepath, streamed=False, action=None, chunk_size=1024, + **kwargs): """Generate the content of a file for this commit. Args: @@ -988,10 +999,9 @@ def blob(self, filepath, **kwargs): 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._raw_get(url, **kwargs) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError) - - return r.content + return utils.response_content(r, streamed, action, chunk_size) def builds(self, **kwargs): """List the build for this commit. @@ -1734,7 +1744,8 @@ def blob(self, sha, filepath, **kwargs): DeprecationWarning) return self.repository_blob(sha, filepath, **kwargs) - def repository_blob(self, sha, filepath, **kwargs): + def repository_blob(self, sha, filepath, streamed=False, action=None, + chunk_size=1024, **kwargs): """Return the content of a file for a commit. Args: @@ -1750,11 +1761,12 @@ def repository_blob(self, sha, filepath, **kwargs): """ url = "/projects/%s/repository/blobs/%s" % (self.id, sha) url += '?filepath=%s' % (filepath) - r = self.gitlab._raw_get(url, **kwargs) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError) - return r.content + return utils.response_content(r, streamed, action, chunk_size) - def repository_raw_blob(self, sha, **kwargs): + def repository_raw_blob(self, sha, streamed=False, action=None, + chunk_size=1024, **kwargs): """Returns the raw file contents for a blob by blob SHA. Args: @@ -1768,9 +1780,9 @@ def repository_raw_blob(self, sha, **kwargs): GitlabGetError: If the server fails to perform the request. """ url = "/projects/%s/repository/raw_blobs/%s" % (self.id, sha) - r = self.gitlab._raw_get(url, **kwargs) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError) - return r.content + return utils.response_content(r, streamed, action, chunk_size) def repository_compare(self, from_, to, **kwargs): """Returns a diff between two branches/commits. @@ -1813,7 +1825,8 @@ def archive(self, sha=None, **kwargs): DeprecationWarning) return self.repository_archive(sha, **kwargs) - def repository_archive(self, sha=None, **kwargs): + def repository_archive(self, sha=None, streamed=False, action=None, + chunk_size=1024, **kwargs): """Return a tarball of the repository. Args: @@ -1829,9 +1842,9 @@ def repository_archive(self, sha=None, **kwargs): url = '/projects/%s/repository/archive' % self.id if sha: url += '?sha=%s' % sha - r = self.gitlab._raw_get(url, **kwargs) + r = self.gitlab._raw_get(url, streamed=streamed, **kwargs) raise_error_from_response(r, GitlabGetError) - return r.content + return utils.response_content(r, streamed, action, chunk_size) def create_file(self, path, branch, content, message, **kwargs): """Creates file in project repository diff --git a/gitlab/utils.py b/gitlab/utils.py new file mode 100644 index 000000000..181ca2056 --- /dev/null +++ b/gitlab/utils.py @@ -0,0 +1,15 @@ +class _StdoutStream(object): + def __call__(self, chunk): + print(chunk) + + +def response_content(response, streamed, action, chunk_size): + if streamed is False: + return response.content + + if action is None: + action = _StdoutStream() + + for chunk in response.iter_content(chunk_size=chunk_size): + if chunk: + action(chunk) From 075345aa3c06e0c29a2edd1dec219c445fc1f220 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 15:56:31 +0200 Subject: [PATCH 54/85] Add missing args in docstrings --- gitlab/objects.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index a8b92de5e..16128afe7 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -917,6 +917,14 @@ def artifacts(self, streamed=False, action=None, chunk_size=1024, def trace(self, streamed=False, action=None, chunk_size=1024, **kwargs): """Get the build trace. + Args: + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. + Returns: str: The trace. @@ -988,6 +996,12 @@ def blob(self, filepath, streamed=False, action=None, chunk_size=1024, Args: filepath (str): Path of the file to request. + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. Returns: str: The content of the file @@ -1751,6 +1765,12 @@ def repository_blob(self, sha, filepath, streamed=False, action=None, Args: sha (str): ID of the commit filepath (str): Path of the file to return + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. Returns: str: The file content @@ -1771,6 +1791,12 @@ def repository_raw_blob(self, sha, streamed=False, action=None, Args: sha(str): ID of the blob + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. Returns: str: The blob content @@ -1831,6 +1857,12 @@ def repository_archive(self, sha=None, streamed=False, action=None, Args: sha (str): ID of the commit (default branch by default). + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. Returns: str: The binary data of the archive. From 048b1cfbe5cb058dda088d7d0020dcd2aa49cc49 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 16:27:54 +0200 Subject: [PATCH 55/85] Groups can be updated --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 16128afe7..fd622cf62 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -712,10 +712,10 @@ class GroupProjectManager(BaseManager): class Group(GitlabObject): _url = '/groups' - canUpdate = False _constructorTypes = {'projects': 'Project'} requiredCreateAttrs = ['name', 'path'] optionalCreateAttrs = ['description', 'visibility_level'] + optionalUpdateAttrs = ['name', 'path', 'description', 'visibility_level'] shortPrintAttr = 'name' managers = [('members', GroupMemberManager, [('group_id', 'id')]), ('projects', GroupProjectManager, [('group_id', 'id')])] From 4d871aadfaa9f57f5ae9f8b49f8367a5ef58545d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 16:41:15 +0200 Subject: [PATCH 56/85] docs: groups API documentation --- docs/api-objects.rst | 1 + docs/gl_objects/groups.py | 66 ++++++++++++++++++++++ docs/gl_objects/groups.rst | 111 +++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 docs/gl_objects/groups.py create mode 100644 docs/gl_objects/groups.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 1212fb68f..8ae7f3356 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -6,6 +6,7 @@ API objects manipulation gl_objects/branches gl_objects/builds + gl_objects/groups gl_objects/projects gl_objects/runners gl_objects/users diff --git a/docs/gl_objects/groups.py b/docs/gl_objects/groups.py new file mode 100644 index 000000000..913c9349f --- /dev/null +++ b/docs/gl_objects/groups.py @@ -0,0 +1,66 @@ +# list +groups = gl.groups.list() +# end list + +# search +groups = gl.groups.search('group') +# end search + +# get +group = gl.groups.get(group_id) +# end get + +# projects list +projects = group.projects.list() +# or +projects = gl.group_projects.list(group_id) +# end projects list + +# create +group = gl.groups.create({'name': 'group1', 'path': 'group1'}) +# end create + +# update +group.description = 'My awesome group' +group.save() +# end update + +# delete +gl.group.delete(group_id) +# or +group.delete() +# end delete + +# member list +members = gl.group_members.list(group_id=1) +# or +members = group.members.list() +# end member list + +# member get +members = gl.group_members.get(member_id) +# or +members = group.members.get(member_id) +# end member get + +# member create +member = gl.group_members.create({'user_id': user_id, + 'access_level': Group.GUEST_ACCESS}, + group_id=1) +# or +member = group.members.create({'user_id': user_id, + 'access_level': Group.GUEST_ACCESS}) +# end member create + +# member update +member.access_level = Group.DEVELOPER_ACCESS +member.save() +# end member update + +# member delete +gl.group_members.delete(member_id, group_id=1) +# or +group.members.delete(member_id) +# or +member.delete() +# end member delete diff --git a/docs/gl_objects/groups.rst b/docs/gl_objects/groups.rst new file mode 100644 index 000000000..b2c0ed865 --- /dev/null +++ b/docs/gl_objects/groups.rst @@ -0,0 +1,111 @@ +###### +Groups +###### + +Groups +====== + +Use :class:`~gitlab.objects.Group` objects to manipulate groups. The +:attr:`gitlab.Gitlab.groups` manager object provides helper functions. + +Examples +-------- + +List the groups: + +.. literalinclude:: groups.py + :start-after: # list + :end-before: # end list + +Search groups: + +.. literalinclude:: groups.py + :start-after: # search + :end-before: # end search + +Get a group's detail: + +.. literalinclude:: groups.py + :start-after: # get + :end-before: # end get + +List a group's projects: + +.. literalinclude:: groups.py + :start-after: # projects list + :end-before: # end projects list + +You can filter and sort the result using the following parameters: + +* ``archived``: limit by archived status +* ``visibility``: limit by visibility. Allowed values are ``public``, + ``internal`` and ``private`` +* ``search``: limit to groups matching the given value +* ``order_by``: sort by criteria. Allowed values are ``id``, ``name``, ``path``, + ``created_at``, ``updated_at`` and ``last_activity_at`` +* ``sort``: sort order: ``asc`` or ``desc`` +* ``ci_enabled_first``: return CI enabled groups first + +Create a group: + +.. literalinclude:: groups.py + :start-after: # create + :end-before: # end create + +Update a group: + +.. literalinclude:: groups.py + :start-after: # update + :end-before: # end update + +Remove a group: + +.. literalinclude:: groups.py + :start-after: # delete + :end-before: # end delete + +Group members +============= + +Use :class:`~gitlab.objects.GroupMember` objects to manipulate groups. The +:attr:`gitlab.Gitlab.group_members` and :attr:`Group.members +` manager objects provide helper functions. + +The following :class:`~gitlab.objects.Group` attributes define the supported +access levels: + +* ``GUEST_ACCESS = 10`` +* ``REPORTER_ACCESS = 20`` +* ``DEVELOPER_ACCESS = 30`` +* ``MASTER_ACCESS = 40`` +* ``OWNER_ACCESS = 50`` + +List group members: + +.. literalinclude:: groups.py + :start-after: # member list + :end-before: # end member list + +Get a group member: + +.. literalinclude:: groups.py + :start-after: # member get + :end-before: # end member get + +Add a member to the group: + +.. literalinclude:: groups.py + :start-after: # member create + :end-before: # end member create + +Update a member (change the access level): + +.. literalinclude:: groups.py + :start-after: # member update + :end-before: # end member update + +Remove a member from the group: + +.. literalinclude:: groups.py + :start-after: # member delete + :end-before: # end member delete From 3198eadb748593de5ac803bc49926300c2849a4f Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 16:57:11 +0200 Subject: [PATCH 57/85] fix unit tests --- gitlab/tests/test_gitlab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index 1f15d305b..c32a56102 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -490,7 +490,7 @@ def resp_cont(url, request): self.assertEqual(expected, data) def test_update_kw_missing(self): - obj = Group(self.gl, data={"name": "testgroup"}) + obj = Hook(self.gl, data={"name": "testgroup"}) self.assertRaises(GitlabUpdateError, self.gl.update, obj) def test_update_401(self): From 832ed9f4c23ba3cb4fba68f1083dbabfa9fe32a7 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 17 Jul 2016 19:13:10 +0200 Subject: [PATCH 58/85] Replace Snippet.Content() with a new content() method This new method use the standard lowercase name and implements data streaming. --- gitlab/objects.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index fd622cf62..9bfb6ec03 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1539,11 +1539,9 @@ class ProjectSnippet(GitlabObject): [('project_id', 'project_id'), ('snippet_id', 'id')])] def Content(self, **kwargs): - url = ("/projects/%(project_id)s/snippets/%(snippet_id)s/raw" % - {'project_id': self.project_id, 'snippet_id': self.id}) - r = self.gitlab._raw_get(url, **kwargs) - raise_error_from_response(r, GitlabGetError) - return r.content + warnings.warn("`Content` is deprecated, use `content` instead", + DeprecationWarning) + return self.content() def Note(self, id=None, **kwargs): warnings.warn("`Note` is deprecated, use `notes` instead", @@ -1554,6 +1552,30 @@ def Note(self, id=None, **kwargs): snippet_id=self.id, **kwargs) + def content(self, streamed=False, action=None, chunk_size=1024, **kwargs): + """Return the raw content of a snippet. + + Args: + streamed (bool): If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment. + action (callable): Callable responsible of dealing with chunk of + data. + chunk_size (int): Size of each chunk. + + Returns: + str: The snippet content + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabGetError: If the server fails to perform the request. + """ + url = ("/projects/%(project_id)s/snippets/%(snippet_id)s/raw" % + {'project_id': self.project_id, 'snippet_id': self.id}) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabGetError) + return utils.response_content(r, streamed, action, chunk_size) + class ProjectSnippetManager(BaseManager): obj_cls = ProjectSnippet From 58ddf1d52d184243a40a754b80275dc3882ccacd Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 18 Jul 2016 09:17:51 +0200 Subject: [PATCH 59/85] doc: fix doubled parameter --- docs/gl_objects/builds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/builds.py b/docs/gl_objects/builds.py index c535cb294..b0d7ea2c7 100644 --- a/docs/gl_objects/builds.py +++ b/docs/gl_objects/builds.py @@ -86,7 +86,7 @@ def __call__(self, chunk): self._fd.write(chunk) target = Foo() -build.artifacts(streamed=True, streamed=True, action=target) +build.artifacts(streamed=True, action=target) del(target) # flushes data on disk # end stream artifacts From 99d0177b3f4d4d08e8c021809eb01d4744ea32fd Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 18 Jul 2016 23:15:54 +0200 Subject: [PATCH 60/85] CLI: refactor _die() --- gitlab/cli.py | 71 ++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/gitlab/cli.py b/gitlab/cli.py index 8739854d1..9f7f4143d 100644 --- a/gitlab/cli.py +++ b/gitlab/cli.py @@ -73,7 +73,9 @@ } -def _die(msg): +def _die(msg, e=None): + if e: + msg = "%s (%s)" % (msg, e) sys.stderr.write(msg + "\n") sys.exit(1) @@ -111,7 +113,7 @@ def do_create(self, cls, gl, what, args): try: o = cls.create(gl, args) except Exception as e: - _die("Impossible to create object (%s)" % str(e)) + _die("Impossible to create object", e) return o @@ -122,7 +124,7 @@ def do_list(self, cls, gl, what, args): try: l = cls.list(gl, **args) except Exception as e: - _die("Impossible to list objects (%s)" % str(e)) + _die("Impossible to list objects", e) return l @@ -137,7 +139,7 @@ def do_get(self, cls, gl, what, args): try: o = cls.get(gl, id, **args) except Exception as e: - _die("Impossible to get object (%s)" % str(e)) + _die("Impossible to get object", e) return o @@ -149,7 +151,7 @@ def do_delete(self, cls, gl, what, args): try: gl.delete(cls, id, **args) except Exception as e: - _die("Impossible to destroy object (%s)" % str(e)) + _die("Impossible to destroy object", e) def do_update(self, cls, gl, what, args): if not cls.canUpdate: @@ -161,7 +163,7 @@ def do_update(self, cls, gl, what, args): o.__dict__[k] = v o.save() except Exception as e: - _die("Impossible to update object (%s)" % str(e)) + _die("Impossible to update object", e) return o @@ -169,165 +171,164 @@ def do_group_search(self, cls, gl, what, args): try: return gl.groups.search(args['query']) except Exception as e: - _die("Impossible to search projects (%s)" % str(e)) + _die("Impossible to search projects", e) def do_project_search(self, cls, gl, what, args): try: return gl.projects.search(args['query']) except Exception as e: - _die("Impossible to search projects (%s)" % str(e)) + _die("Impossible to search projects", e) def do_project_all(self, cls, gl, what, args): try: return gl.projects.all() except Exception as e: - _die("Impossible to list all projects (%s)" % str(e)) + _die("Impossible to list all projects", e) def do_project_starred(self, cls, gl, what, args): try: return gl.projects.starred() except Exception as e: - _die("Impossible to list starred projects (%s)" % str(e)) + _die("Impossible to list starred projects", e) def do_project_owned(self, cls, gl, what, args): try: return gl.projects.owned() except Exception as e: - _die("Impossible to list owned projects (%s)" % str(e)) + _die("Impossible to list owned projects", e) def do_project_star(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.star() except Exception as e: - _die("Impossible to star project (%s)" % str(e)) + _die("Impossible to star project", e) def do_project_unstar(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.unstar() except Exception as e: - _die("Impossible to unstar project (%s)" % str(e)) + _die("Impossible to unstar project", e) def do_project_archive(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.archive_() except Exception as e: - _die("Impossible to archive project (%s)" % str(e)) + _die("Impossible to archive project", e) def do_project_unarchive(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.unarchive_() except Exception as e: - _die("Impossible to unarchive project (%s)" % str(e)) + _die("Impossible to unarchive project", e) def do_project_share(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.share(args['group_id'], args['group_access']) except Exception as e: - _die("Impossible to share project (%s)" % str(e)) + _die("Impossible to share project", e) def do_user_block(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.block() except Exception as e: - _die("Impossible to block user (%s)" % str(e)) + _die("Impossible to block user", e) def do_user_unblock(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.unblock() except Exception as e: - _die("Impossible to block user (%s)" % str(e)) + _die("Impossible to block user", e) def do_project_commit_diff(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return [x['diff'] for x in o.diff()] except Exception as e: - _die("Impossible to get commit diff (%s)" % str(e)) + _die("Impossible to get commit diff", e) def do_project_commit_blob(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.blob(args['filepath']) except Exception as e: - _die("Impossible to get commit blob (%s)" % str(e)) + _die("Impossible to get commit blob", e) def do_project_commit_builds(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.builds() except Exception as e: - _die("Impossible to get commit builds (%s)" % str(e)) + _die("Impossible to get commit builds", e) def do_project_build_cancel(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.cancel() except Exception as e: - _die("Impossible to cancel project build (%s)" % str(e)) + _die("Impossible to cancel project build", e) def do_project_build_retry(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.retry() except Exception as e: - _die("Impossible to retry project build (%s)" % str(e)) + _die("Impossible to retry project build", e) def do_project_build_artifacts(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.artifacts() except Exception as e: - _die("Impossible to get project build artifacts (%s)" % str(e)) + _die("Impossible to get project build artifacts", e) def do_project_build_trace(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.trace() except Exception as e: - _die("Impossible to get project build trace (%s)" % str(e)) + _die("Impossible to get project build trace", e) def do_project_issue_subscribe(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.subscribe() except Exception as e: - _die("Impossible to subscribe to issue (%s)" % str(e)) + _die("Impossible to subscribe to issue", e) def do_project_issue_unsubscribe(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.unsubscribe() except Exception as e: - _die("Impossible to subscribe to issue (%s)" % str(e)) + _die("Impossible to subscribe to issue", e) def do_project_issue_move(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) o.move(args['to_project_id']) except Exception as e: - _die("Impossible to move issue (%s)" % str(e)) + _die("Impossible to move issue", e) def do_project_merge_request_closesissues(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.closes_issues() except Exception as e: - _die("Impossible to list issues closed by merge request (%s)" % - str(e)) + _die("Impossible to list issues closed by merge request", e) def do_project_merge_request_cancel(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.cancel_merge_when_build_succeeds() except Exception as e: - _die("Impossible to cancel merge request (%s)" % str(e)) + _die("Impossible to cancel merge request", e) def do_project_merge_request_merge(self, cls, gl, what, args): try: @@ -339,26 +340,26 @@ def do_project_merge_request_merge(self, cls, gl, what, args): should_remove_source_branch=should_remove, merged_when_build_succeeds=build_succeeds) except Exception as e: - _die("Impossible to validate merge request (%s)" % str(e)) + _die("Impossible to validate merge request", e) def do_project_milestone_issues(self, cls, gl, what, args): try: o = self.do_get(cls, gl, what, args) return o.issues() except Exception as e: - _die("Impossible to get milestone issues (%s)" % str(e)) + _die("Impossible to get milestone issues", e) def do_user_search(self, cls, gl, what, args): try: return gl.users.search(args['query']) except Exception as e: - _die("Impossible to search users (%s)" % str(e)) + _die("Impossible to search users", e) def do_user_getbyusername(self, cls, gl, what, args): try: return gl.users.search(args['query']) except Exception as e: - _die("Impossible to get user %s (%s)" % (args['query'], str(e))) + _die("Impossible to get user %s" % args['query'], e) def _populate_sub_parser_by_class(cls, sub_parser): From 741896d5af682de01101ed4e7713b1daecaf7843 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 18 Jul 2016 23:19:14 +0200 Subject: [PATCH 61/85] tests: don't use deprecated Content method --- gitlab/tests/test_gitlabobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitlab/tests/test_gitlabobject.py b/gitlab/tests/test_gitlabobject.py index aea80ca28..ca0149faf 100644 --- a/gitlab/tests/test_gitlabobject.py +++ b/gitlab/tests/test_gitlabobject.py @@ -486,9 +486,9 @@ def resp_content_fail(self, url, request): def test_content(self): with HTTMock(self.resp_content): data = b'content' - content = self.obj.Content() + content = self.obj.content() self.assertEqual(content, data) def test_blob_fail(self): with HTTMock(self.resp_content_fail): - self.assertRaises(GitlabGetError, self.obj.Content) + self.assertRaises(GitlabGetError, self.obj.content) From f0fbefe9f8eef4dd04afd8e98d7eed454ce75590 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 24 Jul 2016 11:03:57 +0200 Subject: [PATCH 62/85] Improve commit statuses and comments Fixes #92 --- gitlab/__init__.py | 22 ++++++++++++++++++---- gitlab/objects.py | 10 ++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index d702f3101..30aad8582 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -81,8 +81,10 @@ class Gitlab(object): builds project_commits (ProjectCommitManager): Manager for GitLab projects commits - project_commitcomments (ProjectCommitCommentManager): Manager for + project_commit_comments (ProjectCommitCommentManager): Manager for GitLab projects commits comments + project_commit_statuses (ProjectCommitStatusManager): Manager for + GitLab projects commits statuses project_keys (ProjectKeyManager): Manager for GitLab projects keys project_events (ProjectEventManager): Manager for GitLab projects events @@ -157,6 +159,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.project_builds = ProjectBuildManager(self) self.project_commits = ProjectCommitManager(self) self.project_commit_comments = ProjectCommitCommentManager(self) + self.project_commit_statuses = ProjectCommitStatusManager(self) self.project_keys = ProjectKeyManager(self) self.project_events = ProjectEventManager(self) self.project_forks = ProjectForkManager(self) @@ -242,14 +245,24 @@ def set_url(self, url): """ self._url = '%s/api/v3' % url - def _construct_url(self, id_, obj, parameters): + def _construct_url(self, id_, obj, parameters, action=None): if 'next_url' in parameters: return parameters['next_url'] args = _sanitize(parameters) + + url_attr = '_url' + if action is not None: + attr = '_%s_url' % action + if hasattr(obj, attr): + url_attr = attr + obj_url = getattr(obj, url_attr) + + # TODO(gpocentek): the following will need an update when we have + # object with both urlPlural and _ACTION_url attributes if id_ is None and obj._urlPlural is not None: url = obj._urlPlural % args else: - url = obj._url % args + url = obj_url % args if id_ is not None: url = '%s%s/%s' % (self._url, url, str(id_)) @@ -589,7 +602,8 @@ def create(self, obj, **kwargs): raise GitlabCreateError('Missing attribute(s): %s' % ", ".join(missing)) - url = self._construct_url(id_=None, obj=obj, parameters=params) + url = self._construct_url(id_=None, obj=obj, parameters=params, + action='create') headers = self._create_headers(content_type="application/json") # build data that can really be sent to server diff --git a/gitlab/objects.py b/gitlab/objects.py index 9bfb6ec03..ea9f9ab3b 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -943,7 +943,8 @@ class ProjectBuildManager(BaseManager): class ProjectCommitStatus(GitlabObject): - _url = '/projects/%(project_id)s/statuses/%(commit_id)s' + _url = '/projects/%(project_id)s/repository/commits/%(commit_id)s/statuses' + _create_url = '/projects/%(project_id)s/statuses/%(commit_id)s' canUpdate = False canDelete = False requiredUrlAttrs = ['project_id', 'commit_id'] @@ -979,6 +980,8 @@ class ProjectCommit(GitlabObject): requiredUrlAttrs = ['project_id'] shortPrintAttr = 'title' managers = [('comments', ProjectCommitCommentManager, + [('project_id', 'project_id'), ('commit_id', 'id')]), + ('statuses', ProjectCommitStatusManager, [('project_id', 'project_id'), ('commit_id', 'id')])] def diff(self, **kwargs): @@ -1623,7 +1626,10 @@ class Project(GitlabObject): ('branches', ProjectBranchManager, [('project_id', 'id')]), ('builds', ProjectBuildManager, [('project_id', 'id')]), ('commits', ProjectCommitManager, [('project_id', 'id')]), - ('commitstatuses', ProjectCommitStatusManager, [('project_id', 'id')]), + ('commit_comments', ProjectCommitCommentManager, + [('project_id', 'id')]), + ('commit_statuses', ProjectCommitStatusManager, + [('project_id', 'id')]), ('events', ProjectEventManager, [('project_id', 'id')]), ('files', ProjectFileManager, [('project_id', 'id')]), ('forks', ProjectForkManager, [('project_id', 'id')]), From 07c55943eebb302bc1b8feaf482d929c83e9ebe1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 24 Jul 2016 12:21:20 +0200 Subject: [PATCH 63/85] docs: commits API --- docs/api-objects.rst | 1 + docs/gl_objects/commits.py | 50 +++++++++++++++++++++ docs/gl_objects/commits.rst | 87 +++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 docs/gl_objects/commits.py create mode 100644 docs/gl_objects/commits.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 8ae7f3356..47daf7422 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -6,6 +6,7 @@ API objects manipulation gl_objects/branches gl_objects/builds + gl_objects/commits gl_objects/groups gl_objects/projects gl_objects/runners diff --git a/docs/gl_objects/commits.py b/docs/gl_objects/commits.py new file mode 100644 index 000000000..30465139e --- /dev/null +++ b/docs/gl_objects/commits.py @@ -0,0 +1,50 @@ +# list +commits = gl.project_commits.list(project_id=1) +# or +commits = project.commits.list() +# end list + +# filter list +commits = project.commits.list(ref_name='my_branch') +commits = project.commits.list(since='2016-01-01T00:00:00Z') +# end filter list + +# get +commit = gl.project_commits.get('e3d5a71b', project_id=1) +# or +commit = project.commits.get('e3d5a71b') +# end get + +# diff +diff = commit.diff() +# end diff + +# comments list +comments = gl.project_commit_comments.list(project_id=1, commit_id='master') +# or +comments = project.commit_comments.list(commit_id='a5fe4c8') +# or +comments = commit.comments.list() +# end comments list + +# comments create +# Global comment +commit = commit.comments.create({'note': 'This is a nice comment'}) +# Comment on a line in a file (on the new version of the file) +commit = commit.comments.create({'note': 'This is another comment', + 'line': 12, + 'line_type': 'new', + 'path': 'README.rst'}) +# end comments create + +# statuses list +statuses = gl.project_commit_statuses.list(project_id=1, commit_id='master') +# or +statuses = project.commit_statuses.list(commit_id='a5fe4c8') +# or +statuses = commit.statuses.list() +# end statuses list + +# statuses set +commit.statuses.create({'state': 'success'}) +# end statuses set diff --git a/docs/gl_objects/commits.rst b/docs/gl_objects/commits.rst new file mode 100644 index 000000000..5a43597a5 --- /dev/null +++ b/docs/gl_objects/commits.rst @@ -0,0 +1,87 @@ +####### +Commits +####### + +Commits +======= + +Use :class:`~gitlab.objects.ProjectCommit` objects to manipulate commits. The +:attr:`gitlab.Gitlab.project_commits` and +:attr:`gitlab.objects.Project.commits` manager objects provide helper +functions. + +Examples +-------- + +List the commits for a project: + +.. literalinclude:: commits.py + :start-after: # list + :end-before: # end list + +You can use the ``ref_name``, ``since`` and ``until`` filters to limit the +results: + +.. literalinclude:: commits.py + :start-after: # filter list + :end-before: # end filter list + +Get a commit detail: + +.. literalinclude:: commits.py + :start-after: # get + :end-before: # end get + +Get the diff for a commit: + +.. literalinclude:: commits.py + :start-after: # diff + :end-before: # end diff + +Commit comments +=============== + +Use :class:`~gitlab.objects.ProjectCommitStatus` objects to manipulate commits. The +:attr:`gitlab.Gitlab.project_commit_comments` and +:attr:`gitlab.objects.Project.commit_comments` and +:attr:`gitlab.objects.ProjectCommit.comments` manager objects provide helper +functions. + +Examples +-------- + +Get the comments for a commit: + +.. literalinclude:: commits.py + :start-after: # comments list + :end-before: # end comments list + +Add a comment on a commit: + +.. literalinclude:: commits.py + :start-after: # comments create + :end-before: # end comments create + +Commit status +============= + +Use :class:`~gitlab.objects.ProjectCommitStatus` objects to manipulate commits. +The :attr:`gitlab.Gitlab.project_commit_statuses`, +:attr:`gitlab.objects.Project.commit_statuses` and +:attr:`gitlab.objects.ProjectCommit.statuses` manager objects provide helper +functions. + +Examples +-------- + +Get the statuses for a commit: + +.. literalinclude:: commits.py + :start-after: # statuses list + :end-before: # end statuses list + +Change the status of a commit: + +.. literalinclude:: commits.py + :start-after: # statuses set + :end-before: # end statuses set From 41cbc32621004aab2cae5f7c14fc60005ef7b966 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 24 Jul 2016 17:56:04 +0200 Subject: [PATCH 64/85] docs: issues API --- docs/api-objects.rst | 1 + docs/gl_objects/builds.rst | 6 +-- docs/gl_objects/issues.py | 65 ++++++++++++++++++++++++++++ docs/gl_objects/issues.rst | 84 ++++++++++++++++++++++++++++++++++++ docs/gl_objects/projects.rst | 2 +- 5 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 docs/gl_objects/issues.py create mode 100644 docs/gl_objects/issues.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 47daf7422..cd50b4ca0 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -8,6 +8,7 @@ API objects manipulation gl_objects/builds gl_objects/commits gl_objects/groups + gl_objects/issues gl_objects/projects gl_objects/runners gl_objects/users diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index ce4cc0e53..1c4c525ad 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -7,7 +7,7 @@ Build triggers Use :class:`~gitlab.objects.ProjectTrigger` objects to manipulate build triggers. The :attr:`gitlab.Gitlab.project_triggers` and -:attr:`gitlab.objects.Projeect.triggers` manager objects provide helper +:attr:`gitlab.objects.Project.triggers` manager objects provide helper functions. Examples @@ -42,7 +42,7 @@ Build variables Use :class:`~gitlab.objects.ProjectVariable` objects to manipulate build variables. The :attr:`gitlab.Gitlab.project_variables` and -:attr:`gitlab.objects.Projeect.variables` manager objects provide helper +:attr:`gitlab.objects.Project.variables` manager objects provide helper functions. Examples @@ -82,7 +82,7 @@ Builds ====== Use :class:`~gitlab.objects.ProjectBuild` objects to manipulate builds. The -:attr:`gitlab.Gitlab.project_builds` and :attr:`gitlab.objects.Projeect.builds` +:attr:`gitlab.Gitlab.project_builds` and :attr:`gitlab.objects.Project.builds` manager objects provide helper functions. Examples diff --git a/docs/gl_objects/issues.py b/docs/gl_objects/issues.py new file mode 100644 index 000000000..e0eadbedc --- /dev/null +++ b/docs/gl_objects/issues.py @@ -0,0 +1,65 @@ +# list +issues = gl.issues.list() +# end list + +# filtered list +open_issues = gl.issues.list(state='opened') +closed_issues = gl.issues.list(state='closed') +tagged_issues = gl.issues.list(labels=['foo', 'bar']) +# end filtered list + +# project issues list +issues = gl.project_issues.list(project_id=1) +# or +issues = project.issues.list() +# Filter using the state, labels and milestone parameters +issues = project.issues.list(milestone='1.0', state='opened') +# Order using the order_by and sort parameters +issues = project.issues.list(order_by='created_at', sort='desc') +# end project issues list + +# project issues get +issue = gl.project_issues.get(issue_id, project_id=1) +# or +issue = project.issues.get(issue_id) +# end project issues get + +# project issues create +issue = gl.project_issues.create({'title': 'I have a bug', + 'description': 'Something useful here.'}, + project_id=1) +# or +issue = project.issues.create({'title': 'I have a bug', + 'description': 'Something useful here.'}) +# end project issues create + +# project issue update +issue.labels = ['foo', 'bar'] +issue.save() +# end project issue update + +# project issue open_close +# close an issue +issue.state_event = 'close' +issue.save() +# reopen it +issue.state_event = 'reopen' +issue.save() +# end project issue open_close + +# project issue delete +gl.project_issues.delete(issue_id, project_id=1) +# or +project.issues.delete(issue_id) +# pr +issue.delete() +# end project issue delete + +# project issue subscribe +issue.subscribe() +issue.unsubscribe() +# end project issue subscribe + +# project issue move +issue.move(new_project_id) +# end project issue move diff --git a/docs/gl_objects/issues.rst b/docs/gl_objects/issues.rst new file mode 100644 index 000000000..335ec5420 --- /dev/null +++ b/docs/gl_objects/issues.rst @@ -0,0 +1,84 @@ +###### +Issues +###### + +Reported issues +=============== + +Use :class:`~gitlab.objects.Issues` objects to manipulate issues the +authenticated user reported. The :attr:`gitlab.Gitlab.issues` manager object +provides helper functions. + +Examples +-------- + +List the issues: + +.. literalinclude:: issues.py + :start-after: # list + :end-before: # end list + +Use the ``state`` and ``label`` parameters to filter the results. Use the +``order_by`` and ``sort`` attributes to sort the results: + +.. literalinclude:: issues.py + :start-after: # filtered list + :end-before: # end filtered list + +Project issues +============== + +Use :class:`~gitlab.objects.ProjectIssue` objects to manipulate issues. The +:attr:`gitlab.Gitlab.project_issues` and :attr:`Project.issues +` manager objects provide helper functions. + +Examples +-------- + +List the project issues: + +.. literalinclude:: issues.py + :start-after: # project issues list + :end-before: # end project issues list + +Get a project issue: + +.. literalinclude:: issues.py + :start-after: # project issues get + :end-before: # end project issues get + +Create a new issue: + +.. literalinclude:: issues.py + :start-after: # project issues create + :end-before: # end project issues create + +Update an issue: + +.. literalinclude:: issues.py + :start-after: # project issue update + :end-before: # end project issue update + +Close / reopen an issue: + +.. literalinclude:: issues.py + :start-after: # project issue open_close + :end-before: # end project issue open_close + +Delete an issue: + +.. literalinclude:: issues.py + :start-after: # project issue delete + :end-before: # end project issue delete + +Subscribe / unsubscribe from an issue: + +.. literalinclude:: issues.py + :start-after: # project issue subscribe + :end-before: # end project issue subscribe + +Move an issue to another project: + +.. literalinclude:: issues.py + :start-after: # project issue move + :end-before: # end project issue move diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index e9c5b6cea..294c3f2f8 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -91,7 +91,7 @@ Archive/unarchive a project: Events ------ -Use :class:`~gitlab.objects.ProjectEvent` objects to manipulate projects. The +Use :class:`~gitlab.objects.ProjectEvent` objects to manipulate events. The :attr:`gitlab.Gitlab.project_events` and :attr:`Project.events ` manager objects provide helper functions. From 261f9470f457673e8082e673fb09861a993fdabc Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 24 Jul 2016 17:58:30 +0200 Subject: [PATCH 65/85] Docs: drop the FAQ The only question is now documented in the API examples. --- docs/faq.rst | 15 --------------- docs/index.rst | 1 - 2 files changed, 16 deletions(-) delete mode 100644 docs/faq.rst diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index 7c35567bd..000000000 --- a/docs/faq.rst +++ /dev/null @@ -1,15 +0,0 @@ -### -FAQ -### - -How can I close or reopen an issue? -=================================== - - -Set the issue ``state_event`` attribute to ``close`` or ``reopen`` and save the object: - -.. code-block:: python - - issue = my_project.issues.get(issue_id) - issue.state_event = 'close' - issue.save() diff --git a/docs/index.rst b/docs/index.rst index f38aea895..54472fe43 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,7 +15,6 @@ Contents: cli api-usage api-objects - faq upgrade-from-0.10 api/modules From 580f21ea9a80bfe7062296ac7684489d5375df69 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 24 Jul 2016 18:06:29 +0200 Subject: [PATCH 66/85] Add support from listing group issues --- docs/gl_objects/issues.py | 10 ++++++++++ docs/gl_objects/issues.rst | 16 ++++++++++++++++ gitlab/__init__.py | 2 ++ gitlab/objects.py | 17 ++++++++++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/issues.py b/docs/gl_objects/issues.py index e0eadbedc..a378910d9 100644 --- a/docs/gl_objects/issues.py +++ b/docs/gl_objects/issues.py @@ -8,6 +8,16 @@ tagged_issues = gl.issues.list(labels=['foo', 'bar']) # end filtered list +# group issues list +issues = gl.group_issues.list(group_id=1) +# or +issues = group.issues.list() +# Filter using the state, labels and milestone parameters +issues = group.issues.list(milestone='1.0', state='opened') +# Order using the order_by and sort parameters +issues = group.issues.list(order_by='created_at', sort='desc') +# end group issues list + # project issues list issues = gl.project_issues.list(project_id=1) # or diff --git a/docs/gl_objects/issues.rst b/docs/gl_objects/issues.rst index 335ec5420..ac230439e 100644 --- a/docs/gl_objects/issues.rst +++ b/docs/gl_objects/issues.rst @@ -25,6 +25,22 @@ Use the ``state`` and ``label`` parameters to filter the results. Use the :start-after: # filtered list :end-before: # end filtered list +Group issues +============ + +Use :class:`~gitlab.objects.GroupIssue` objects to manipulate issues. The +:attr:`gitlab.Gitlab.project_issues` and :attr:`Group.issues +` manager objects provide helper functions. + +Examples +-------- + +List the group issues: + +.. literalinclude:: issues.py + :start-after: # group issues list + :end-before: # end group issues list + Project issues ============== diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 30aad8582..f81918662 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -68,6 +68,7 @@ class Gitlab(object): user_emails (UserEmailManager): Manager for GitLab users' emails. user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users + group_issues (GroupIssueManager): Manager for GitLab group issues group_projects (GroupProjectManager): Manager for GitLab group projects group_members (GroupMemberManager): Manager for GitLab group members groups (GroupManager): Manager for GitLab members @@ -148,6 +149,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.user_emails = UserEmailManager(self) self.user_keys = UserKeyManager(self) self.users = UserManager(self) + self.group_issues = GroupIssueManager(self) self.group_projects = GroupProjectManager(self) self.group_members = GroupMemberManager(self) self.groups = GroupManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index ea9f9ab3b..315abdea0 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -679,6 +679,20 @@ class ApplicationSettingsManager(BaseManager): obj_cls = ApplicationSettings +class GroupIssue(GitlabObject): + _url = '/groups/%(group_id)s/issues' + canGet = 'from_list' + canCreate = False + canUpdate = False + canDelete = False + requiredUrlAttrs = ['group_id'] + optionalListAttrs = ['state', 'labels', 'milestone', 'order_by', 'sort'] + + +class GroupIssueManager(BaseManager): + obj_cls = GroupIssue + + class GroupMember(GitlabObject): _url = '/groups/%(group_id)s/members' canGet = 'from_list' @@ -718,7 +732,8 @@ class Group(GitlabObject): optionalUpdateAttrs = ['name', 'path', 'description', 'visibility_level'] shortPrintAttr = 'name' managers = [('members', GroupMemberManager, [('group_id', 'id')]), - ('projects', GroupProjectManager, [('group_id', 'id')])] + ('projects', GroupProjectManager, [('group_id', 'id')]), + ('issues', GroupIssueManager, [('group_id', 'id')])] GUEST_ACCESS = 10 REPORTER_ACCESS = 20 From d61510e55f73ed328dde66f8a6c1138d554ab000 Mon Sep 17 00:00:00 2001 From: Christian Wenk Date: Mon, 25 Jul 2016 10:28:51 +0200 Subject: [PATCH 67/85] Added a new project attribute to enable the container registry. --- gitlab/objects.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 315abdea0..42d5b38ee 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1627,12 +1627,14 @@ class Project(GitlabObject): requiredCreateAttrs = ['name'] optionalCreateAttrs = ['default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', 'wiki_enabled', - 'snippets_enabled', 'public', 'visibility_level', - 'namespace_id', 'description', 'path', 'import_url', + 'snippets_enabled', 'container_registry_enabled', + 'public', 'visibility_level', 'namespace_id', + 'description', 'path', 'import_url', 'builds_enabled', 'public_builds'] optionalUpdateAttrs = ['name', 'default_branch', 'issues_enabled', 'wall_enabled', 'merge_requests_enabled', - 'wiki_enabled', 'snippets_enabled', 'public', + 'wiki_enabled', 'snippets_enabled', + 'container_registry_enabled', 'public', 'visibility_level', 'namespace_id', 'description', 'path', 'import_url', 'builds_enabled', 'public_builds'] From 1f52cd2df35dab33dbf7429c8d514443278b549a Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 4 Aug 2016 08:27:18 +0200 Subject: [PATCH 68/85] document namespaces API --- docs/api-objects.rst | 1 + docs/gl_objects/namespaces.py | 7 +++++++ docs/gl_objects/namespaces.rst | 21 +++++++++++++++++++++ docs/gl_objects/runners.rst | 2 +- 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 docs/gl_objects/namespaces.py create mode 100644 docs/gl_objects/namespaces.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index cd50b4ca0..da093d994 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -9,6 +9,7 @@ API objects manipulation gl_objects/commits gl_objects/groups gl_objects/issues + gl_objects/namespaces gl_objects/projects gl_objects/runners gl_objects/users diff --git a/docs/gl_objects/namespaces.py b/docs/gl_objects/namespaces.py new file mode 100644 index 000000000..fe5069757 --- /dev/null +++ b/docs/gl_objects/namespaces.py @@ -0,0 +1,7 @@ +# list +namespaces = gl.namespaces.list() +# end list + +# search +namespaces = gl.namespaces.list(search='foo') +# end search diff --git a/docs/gl_objects/namespaces.rst b/docs/gl_objects/namespaces.rst new file mode 100644 index 000000000..1819180b9 --- /dev/null +++ b/docs/gl_objects/namespaces.rst @@ -0,0 +1,21 @@ +########## +Namespaces +########## + +Use :class:`~gitlab.objects.Namespace` objects to manipulate namespaces. The +:attr:`gitlab.Gitlab.namespaces` manager objects provides helper functions. + +Examples +======== + +List namespaces: + +.. literalinclude:: namespaces.py + :start-after: # list + :end-before: # end list + +Search namespaces: + +.. literalinclude:: namespaces.py + :start-after: # search + :end-before: # end search diff --git a/docs/gl_objects/runners.rst b/docs/gl_objects/runners.rst index 08c4bc719..32d671999 100644 --- a/docs/gl_objects/runners.rst +++ b/docs/gl_objects/runners.rst @@ -13,7 +13,7 @@ Examples Use the ``list()`` and ``all()`` methods to list runners. - The ``all()`` method accepts a ``scope`` parameter to filter the list. Allowed +The ``all()`` method accepts a ``scope`` parameter to filter the list. Allowed values for this parameter are ``specific``, ``shared``, ``active``, ``paused`` and ``online``. From e1f5e1560e53019d45b113a71916ad9a7695afeb Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Thu, 4 Aug 2016 08:32:12 +0200 Subject: [PATCH 69/85] doc: replace incorrect archive call() --- docs/api-usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index ca85fbdd8..976a3a08a 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -99,9 +99,9 @@ actions on the GitLab resources. For example: .. code-block:: python - # get a tarball of the git repository + # star a git repository project = gl.projects.get(1) - project.archive() + project.star() Pagination ========== From e6ffd69bc745ce1a5b857fc248a3bef793e30138 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 14:26:23 +0200 Subject: [PATCH 70/85] add a contributing section in README --- README.rst | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ab3f77b9b..534d28a41 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ .. image:: https://travis-ci.org/gpocentek/python-gitlab.svg?branch=master :target: https://travis-ci.org/gpocentek/python-gitlab - + Python GitLab ============= @@ -36,5 +36,73 @@ https://github.com/gpocentek/python-gitlab/issues. Documentation ============= -The documentation for CLI and API is available on `readthedocs +The full documentation for CLI and API is available on `readthedocs `_. + + +Contributing +============ + +You can contribute to the project in multiple ways: + +* Write documentation +* Implement features +* Fix bugs +* Add unit and functional tests +* Everything else you can think of + +Provide your patches as github pull requests. Thanks! + +Running unit tests +------------------ + +Before submitting a pull request make sure that the tests still succeed with +your change. Unit tests will run using the travis service and passing tests are +mandatory. + +You need to install ``tox`` to run unit tests and documentation builds: + +.. code-block:: bash + + # run the unit tests for python 2/3, and the pep8 tests: + tox + + # run tests in one environment only: + tox -epy35 + + # build the documentation, the result will be generated in + # build/sphinx/html/ + tox -edocs + +Running integration tests +------------------------- + +Two scripts run tests against a running gitlab instance, using a docker +container. You need to have docker installed on the test machine, and your user +must have the correct permissions to talk to the docker daemon. + +To run these tests: + +.. code-block:: bash + + # run the CLI tests: + ./tools/functional_tests.sh + + # run the python API tests: + ./tools/py_functional_tests.sh + +You can also build a test environment using the following command: + +.. code-block:: bash + + ./tools/build_test_env.sh + +A freshly configured gitlab container will be available at +http://localhost:8080 (login ``root`` / password ``5iveL!fe``). A configuration +for python-gitlab will be written in ``/tmp/python-gitlab.cfg``. + +To cleanup the environement delete the container: + +.. code-block:: bash + + docker rm -f gitlab-test From 9bd2cb70b255b5ec8c2112d186a829f78c1bb6be Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 20:58:27 +0200 Subject: [PATCH 71/85] add support for global deploy key listing --- gitlab/__init__.py | 2 ++ gitlab/objects.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index f81918662..5baa55d53 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -68,6 +68,7 @@ class Gitlab(object): user_emails (UserEmailManager): Manager for GitLab users' emails. user_keys (UserKeyManager): Manager for GitLab users' SSH keys. users (UserManager): Manager for GitLab users + keys (DeployKeyManager): Manager for deploy keys group_issues (GroupIssueManager): Manager for GitLab group issues group_projects (GroupProjectManager): Manager for GitLab group projects group_members (GroupMemberManager): Manager for GitLab group members @@ -149,6 +150,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.user_emails = UserEmailManager(self) self.user_keys = UserKeyManager(self) self.users = UserManager(self) + self.keys = KeyManager(self) self.group_issues = GroupIssueManager(self) self.group_projects = GroupProjectManager(self) self.group_members = GroupMemberManager(self) diff --git a/gitlab/objects.py b/gitlab/objects.py index 42d5b38ee..76cc21919 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -679,6 +679,18 @@ class ApplicationSettingsManager(BaseManager): obj_cls = ApplicationSettings +class Key(GitlabObject): + _url = '/deploy_keys' + canGet = 'from_list' + canCreate = False + canUpdate = False + canDelete = False + + +class KeyManager(BaseManager): + obj_cls = Key + + class GroupIssue(GitlabObject): _url = '/groups/%(group_id)s/issues' canGet = 'from_list' From ea089e092439a8fe95b50c3d0592358550389b51 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 21:18:02 +0200 Subject: [PATCH 72/85] docs: add deploy keys API --- docs/api-objects.rst | 1 + docs/gl_objects/deploy_keys.py | 36 ++++++++++++++++++++ docs/gl_objects/deploy_keys.rst | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 docs/gl_objects/deploy_keys.py create mode 100644 docs/gl_objects/deploy_keys.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index da093d994..5a7194712 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -7,6 +7,7 @@ API objects manipulation gl_objects/branches gl_objects/builds gl_objects/commits + gl_objects/deploy_keys gl_objects/groups gl_objects/issues gl_objects/namespaces diff --git a/docs/gl_objects/deploy_keys.py b/docs/gl_objects/deploy_keys.py new file mode 100644 index 000000000..7a69fa36a --- /dev/null +++ b/docs/gl_objects/deploy_keys.py @@ -0,0 +1,36 @@ +# global list +keys = gl.keys.list() +# end global list + +# global get +key = gl.keys.get(key_id) +# end global key + +# list +keys = gl.project_keys.list(project_id=1) +# or +keys = project.keys.list() +# end list + +# get +key = gl.project_keys.get(key_id, project_id=1) +# or +key = project.keys.get(key_id) +# end get + +# create +key = gl.project_keys.create({'title': 'jenkins key', + 'key': open('/home/me/.ssh/id_rsa.pub').read()}, + project_id=1) +# or +key = project.keys.create({'title': 'jenkins key', + 'key': open('/home/me/.ssh/id_rsa.pub').read()}) +# end create + +# delete +key = gl.project_keys.delete(key_id, project_id=1) +# or +key = project.keys.list(key_id) +# or +key.delete() +# end delete diff --git a/docs/gl_objects/deploy_keys.rst b/docs/gl_objects/deploy_keys.rst new file mode 100644 index 000000000..e67e2c171 --- /dev/null +++ b/docs/gl_objects/deploy_keys.rst @@ -0,0 +1,58 @@ +########### +Deploy keys +########### + +Deploy keys +=========== + +Use :class:`~gitlab.objects.Key` objects to manipulate deploy keys. The +:attr:`gitlab.Gitlab.keys` manager object provides helper functions. + +Examples +-------- + +List the deploy keys: + +.. literalinclude:: deploy_keys.py + :start-after: # global list + :end-before: # end global list + +Get a single deploy key: + +.. literalinclude:: deploy_keys.py + :start-after: # global get + :end-before: # end global get + +Deploy keys for projects +======================== + +Use :class:`~gitlab.objects.ProjectKey` objects to manipulate deploy keys for +projects. The :attr:`gitlab.Gitlab.project_keys` and :attr:`Project.keys +` manager objects provide helper functions. + +Examples +-------- + +List keys for a project: + +.. literalinclude:: deploy_keys.py + :start-after: # list + :end-before: # end list + +Get a single deploy key: + +.. literalinclude:: deploy_keys.py + :start-after: # get + :end-before: # end get + +Create a deploy key for a project: + +.. literalinclude:: deploy_keys.py + :start-after: # create + :end-before: # end create + +Delete a deploy key for a project: + +.. literalinclude:: deploy_keys.py + :start-after: # delete + :end-before: # end delete From 5b08d2a364d0f355c8df9e4926e5a54fc5f15f36 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 21:47:15 +0200 Subject: [PATCH 73/85] Add support for project environments --- docs/api-objects.rst | 2 ++ docs/gl_objects/environments.py | 31 ++++++++++++++++++++++++ docs/gl_objects/environments.rst | 41 ++++++++++++++++++++++++++++++++ gitlab/__init__.py | 4 ++++ gitlab/objects.py | 14 +++++++++++ 5 files changed, 92 insertions(+) create mode 100644 docs/gl_objects/environments.py create mode 100644 docs/gl_objects/environments.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 5a7194712..59d981c72 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -3,11 +3,13 @@ API objects manipulation ######################## .. toctree:: + :maxdepth: 1 gl_objects/branches gl_objects/builds gl_objects/commits gl_objects/deploy_keys + gl_objects/environments gl_objects/groups gl_objects/issues gl_objects/namespaces diff --git a/docs/gl_objects/environments.py b/docs/gl_objects/environments.py new file mode 100644 index 000000000..80d77c922 --- /dev/null +++ b/docs/gl_objects/environments.py @@ -0,0 +1,31 @@ +# list +environments = gl.project_environments.list(project_id=1) +# or +environments = project.environments.list() +# end list + +# get +environment = gl.project_environments.get(environment_id, project_id=1) +# or +environment = project.environments.get(environment_id) +# end get + +# create +environment = gl.project_environments.create({'name': 'production'}, + project_id=1) +# or +environment = project.environments.create({'name': 'production'}) +# end create + +# update +environment.external_url = 'http://foo.bar.com' +environment.save() +# end update + +# delete +environment = gl.project_environments.delete(environment_id, project_id=1) +# or +environment = project.environments.list(environment_id) +# or +environment.delete() +# end delete diff --git a/docs/gl_objects/environments.rst b/docs/gl_objects/environments.rst new file mode 100644 index 000000000..83d080b5c --- /dev/null +++ b/docs/gl_objects/environments.rst @@ -0,0 +1,41 @@ +############ +Environments +############ + +Use :class:`~gitlab.objects.ProjectEnvironment` objects to manipulate +environments for projects. The :attr:`gitlab.Gitlab.project_environments` and +:attr:`Project.environments ` manager +objects provide helper functions. + +Examples +-------- + +List environments for a project: + +.. literalinclude:: environments.py + :start-after: # list + :end-before: # end list + +Get a single environment: + +.. literalinclude:: environments.py + :start-after: # get + :end-before: # end get + +Create an environment for a project: + +.. literalinclude:: environments.py + :start-after: # create + :end-before: # end create + +Update an environment for a project: + +.. literalinclude:: environments.py + :start-after: # update + :end-before: # end update + +Delete an environment for a project: + +.. literalinclude:: environments.py + :start-after: # delete + :end-before: # end delete diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 5baa55d53..9c93cd12d 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -88,6 +88,8 @@ class Gitlab(object): project_commit_statuses (ProjectCommitStatusManager): Manager for GitLab projects commits statuses project_keys (ProjectKeyManager): Manager for GitLab projects keys + project_environments (ProjectEnvironmentManager): Manager for GitLab + projects environments project_events (ProjectEventManager): Manager for GitLab projects events project_forks (ProjectForkManager): Manager for GitLab projects forks @@ -165,6 +167,7 @@ def __init__(self, url, private_token=None, email=None, password=None, self.project_commit_comments = ProjectCommitCommentManager(self) self.project_commit_statuses = ProjectCommitStatusManager(self) self.project_keys = ProjectKeyManager(self) + self.project_environments = ProjectEnvironmentManager(self) self.project_events = ProjectEventManager(self) self.project_forks = ProjectForkManager(self) self.project_hooks = ProjectHookManager(self) @@ -417,6 +420,7 @@ def list(self, obj_class, **kwargs): ", ".join(missing)) url = self._construct_url(id_=None, obj=obj_class, parameters=kwargs) + print(url) headers = self._create_headers() # Remove attributes that are used in url so that there is only diff --git a/gitlab/objects.py b/gitlab/objects.py index 76cc21919..04f9e4778 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1075,6 +1075,19 @@ class ProjectCommitManager(BaseManager): obj_cls = ProjectCommit +class ProjectEnvironment(GitlabObject): + _url = '/projects/%(project_id)s/environments' + canGet = 'from_list' + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['name'] + optionalCreateAttrs = ['external_url'] + optionalUpdateAttrs = ['name', 'external_url'] + + +class ProjectEnvironmentManager(BaseManager): + obj_cls = ProjectEnvironment + + class ProjectKey(GitlabObject): _url = '/projects/%(project_id)s/keys' canUpdate = False @@ -1659,6 +1672,7 @@ class Project(GitlabObject): [('project_id', 'id')]), ('commit_statuses', ProjectCommitStatusManager, [('project_id', 'id')]), + ('environments', ProjectEnvironmentManager, [('project_id', 'id')]), ('events', ProjectEventManager, [('project_id', 'id')]), ('files', ProjectFileManager, [('project_id', 'id')]), ('forks', ProjectForkManager, [('project_id', 'id')]), From 4fd00f8a7a879eb113e3998b1c9ef82758560235 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 21:52:19 +0200 Subject: [PATCH 74/85] remove debug print statement --- gitlab/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 9c93cd12d..8d1212e38 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -420,7 +420,6 @@ def list(self, obj_class, **kwargs): ", ".join(missing)) url = self._construct_url(id_=None, obj=obj_class, parameters=kwargs) - print(url) headers = self._create_headers() # Remove attributes that are used in url so that there is only From 31882b8a57f3f4c7e4c4c4b319af436795ebafd3 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 22:04:00 +0200 Subject: [PATCH 75/85] docs: add labales API --- docs/api-objects.rst | 1 + docs/gl_objects/labels.py | 35 +++++++++++++++++++++++++++++++++ docs/gl_objects/labels.rst | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 docs/gl_objects/labels.py create mode 100644 docs/gl_objects/labels.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 59d981c72..788fff2dc 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -12,6 +12,7 @@ API objects manipulation gl_objects/environments gl_objects/groups gl_objects/issues + gl_objects/labels gl_objects/namespaces gl_objects/projects gl_objects/runners diff --git a/docs/gl_objects/labels.py b/docs/gl_objects/labels.py new file mode 100644 index 000000000..ce5c88d09 --- /dev/null +++ b/docs/gl_objects/labels.py @@ -0,0 +1,35 @@ +# list +labels = gl.project_labels.list(project_id=1) +# or +labels = project.labels.list() +# end list + +# get +label = gl.project_labels.get(label_name, project_id=1) +# or +label = project.labels.get(label_name) +# end get + +# create +label = gl.project_labels.create({'name': 'foo', 'color': '#8899aa'}, + project_id=1) +# or +label = project.labels.create({'name': 'foo', 'color': '#8899aa'}) +# end create + +# update +# change the name of the label: +label.new_name = 'bar' +label.save() +# change its color: +label.color = '#112233' +label.save() +# end update + +# delete +gl.project_labels.delete(label_id, project_id=1) +# or +project.labels.list(label_id) +# or +label.delete() +# end delete diff --git a/docs/gl_objects/labels.rst b/docs/gl_objects/labels.rst new file mode 100644 index 000000000..3973b0b90 --- /dev/null +++ b/docs/gl_objects/labels.rst @@ -0,0 +1,40 @@ +###### +Labels +###### + +Use :class:`~gitlab.objects.ProjectLabel` objects to manipulate labels for +projects. The :attr:`gitlab.Gitlab.project_labels` and :attr:`Project.labels +` manager objects provide helper functions. + +Examples +-------- + +List labels for a project: + +.. literalinclude:: labels.py + :start-after: # list + :end-before: # end list + +Get a single label: + +.. literalinclude:: labels.py + :start-after: # get + :end-before: # end get + +Create a label for a project: + +.. literalinclude:: labels.py + :start-after: # create + :end-before: # end create + +Update a label for a project: + +.. literalinclude:: labels.py + :start-after: # update + :end-before: # end update + +Delete a label for a project: + +.. literalinclude:: labels.py + :start-after: # delete + :end-before: # end delete From 4540614a38067944c628505225bb15928d8e3c93 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 6 Aug 2016 22:12:18 +0200 Subject: [PATCH 76/85] docs: add licenses API --- docs/api-objects.rst | 1 + docs/gl_objects/licenses.py | 8 ++++++++ docs/gl_objects/licenses.rst | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 docs/gl_objects/licenses.py create mode 100644 docs/gl_objects/licenses.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 788fff2dc..bf1a4a6e9 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -13,6 +13,7 @@ API objects manipulation gl_objects/groups gl_objects/issues gl_objects/labels + gl_objects/licenses gl_objects/namespaces gl_objects/projects gl_objects/runners diff --git a/docs/gl_objects/licenses.py b/docs/gl_objects/licenses.py new file mode 100644 index 000000000..425a9a46d --- /dev/null +++ b/docs/gl_objects/licenses.py @@ -0,0 +1,8 @@ +# list +licenses = gl.licenses.list() +# end list + +# get +license = gl.licenses.get('apache-2.0', project='foobar', fullname='John Doe') +print(license.content) +# end get diff --git a/docs/gl_objects/licenses.rst b/docs/gl_objects/licenses.rst new file mode 100644 index 000000000..2b823799e --- /dev/null +++ b/docs/gl_objects/licenses.rst @@ -0,0 +1,21 @@ +######## +Licenses +######## + +Use :class:`~gitlab.objects.License` objects to manipulate licenses. The +:attr:`gitlab.Gitlab.licenses` manager object provides helper functions. + +Examples +-------- + +List known licenses: + +.. literalinclude:: licenses.py + :start-after: # list + :end-before: # end list + +Generate a license content for a project: + +.. literalinclude:: licenses.py + :start-after: # get + :end-before: # end get From 92edb9922b178783f9307c84147352ae31f32d0b Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 09:46:12 +0200 Subject: [PATCH 77/85] MR: get list of changes and commits --- gitlab/objects.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/gitlab/objects.py b/gitlab/objects.py index 04f9e4778..5b607e044 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1415,6 +1415,36 @@ def closes_issues(self, **kwargs): (self.project_id, self.id)) return self.gitlab._raw_list(url, ProjectIssue, **kwargs) + def commits(self, **kwargs): + """List the merge request commits. + + Returns: + list (ProjectCommit): List of commits + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabListError: If the server fails to perform the request. + """ + url = ('/projects/%s/merge_requests/%s/commits' % + (self.project_id, self.id)) + return self.gitlab._raw_list(url, ProjectCommit, **kwargs) + + def changes(self, **kwargs): + """List the merge request changes. + + Returns: + list (dict): List of changes + + Raises: + GitlabConnectionError: If the server cannot be reached. + GitlabListError: If the server fails to perform the request. + """ + url = ('/projects/%s/merge_requests/%s/commits' % + (self.project_id, self.id)) + r = self.gitlab._raw_get(url, **kwargs) + raise_error_from_response(r, GitlabListError) + return r.json() + def merge(self, merge_commit_message=None, should_remove_source_branch=False, merged_when_build_succeeds=False, From 922041d1215dc00ecd633e4fc330fd991ad578bd Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 11:41:09 +0200 Subject: [PATCH 78/85] Fix the listing of some resources The parent ID wasn't available in the generated objects, leading to exceptions when trying to use specific methods for these objects. Fixes #132 --- gitlab/__init__.py | 5 +++-- gitlab/objects.py | 36 +++++++++++++----------------------- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 8d1212e38..15a142ca7 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -323,11 +323,12 @@ def _raw_get(self, path, content_type=None, streamed=False, **kwargs): raise GitlabConnectionError( "Can't connect to GitLab server (%s)" % e) - def _raw_list(self, path, cls, **kwargs): + def _raw_list(self, path, cls, extra_attrs={}, **kwargs): r = self._raw_get(path, **kwargs) raise_error_from_response(r, GitlabListError) - cls_kwargs = kwargs.copy() + cls_kwargs = extra_attrs.copy() + cls_kwargs.update(kwargs.copy()) # Add _from_api manually, because we are not creating objects # through normal path diff --git a/gitlab/objects.py b/gitlab/objects.py index 5b607e044..1ce12279f 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1059,16 +1059,9 @@ def builds(self, **kwargs): """ url = '/projects/%s/repository/commits/%s/builds' % (self.project_id, self.id) - r = self.gitlab._raw_get(url, **kwargs) - raise_error_from_response(r, GitlabListError) - - l = [] - for j in r.json(): - o = ProjectBuild(self, j) - o._from_api = True - l.append(o) - - return l + return self.gitlab._raw_list(url, ProjectBuild, + {'project_id': self.project_id}, + **kwargs) class ProjectCommitManager(BaseManager): @@ -1413,7 +1406,9 @@ def closes_issues(self, **kwargs): """ url = ('/projects/%s/merge_requests/%s/closes_issues' % (self.project_id, self.id)) - return self.gitlab._raw_list(url, ProjectIssue, **kwargs) + return self.gitlab._raw_list(url, ProjectIssue, + {'project_id': self.project_id}, + **kwargs) def commits(self, **kwargs): """List the merge request commits. @@ -1427,7 +1422,9 @@ def commits(self, **kwargs): """ url = ('/projects/%s/merge_requests/%s/commits' % (self.project_id, self.id)) - return self.gitlab._raw_list(url, ProjectCommit, **kwargs) + return self.gitlab._raw_list(url, ProjectCommit, + {'project_id': self.project_id}, + **kwargs) def changes(self, **kwargs): """List the merge request changes. @@ -1497,18 +1494,11 @@ class ProjectMilestone(GitlabObject): optionalUpdateAttrs = requiredCreateAttrs + optionalCreateAttrs shortPrintAttr = 'title' - def issues(self): + def issues(self, **kwargs): url = "/projects/%s/milestones/%s/issues" % (self.project_id, self.id) - r = self.gitlab._raw_get(url) - raise_error_from_response(r, GitlabDeleteError) - - l = [] - for j in r.json(): - o = ProjectIssue(self, j) - o._from_api = True - l.append(o) - - return l + return self.gitlab._raw_list(url, ProjectIssue, + {'project_id': self.project_id}, + **kwargs) class ProjectMilestoneManager(BaseManager): From 71edeeb12139763944e8b205080dbbcc4a4a2a75 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:32:53 +0200 Subject: [PATCH 79/85] fix labels deletion example --- docs/gl_objects/labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/labels.py b/docs/gl_objects/labels.py index ce5c88d09..9a363632c 100644 --- a/docs/gl_objects/labels.py +++ b/docs/gl_objects/labels.py @@ -29,7 +29,7 @@ # delete gl.project_labels.delete(label_id, project_id=1) # or -project.labels.list(label_id) +project.labels.delete(label_id) # or label.delete() # end delete From 4a73b85f89a4d568938bd2785506fa3708ad5c83 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:33:43 +0200 Subject: [PATCH 80/85] MR: fix updates --- gitlab/objects.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 1ce12279f..d46d4fb1d 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1329,6 +1329,11 @@ class ProjectMergeRequest(GitlabObject): requiredCreateAttrs = ['source_branch', 'target_branch', 'title'] optionalCreateAttrs = ['assignee_id', 'description', 'target_project_id', 'labels', 'milestone_id'] + optionalUpdateAttrs = ['target_branch', 'assignee_id', 'title', + 'description', 'state_event', 'labels', + 'milestone_id'] + optionalListAttrs = ['iid', 'state', 'order_by', 'sort'] + managers = [('notes', ProjectMergeRequestNoteManager, [('project_id', 'project_id'), ('merge_request_id', 'id')])] @@ -1341,7 +1346,7 @@ def Note(self, id=None, **kwargs): def _data_for_gitlab(self, extra_parameters={}, update=False): data = (super(ProjectMergeRequest, self) - ._data_for_gitlab(extra_parameters)) + ._data_for_gitlab(extra_parameters, update=update)) if update: # Drop source_branch attribute as it is not accepted by the gitlab # server (Issue #76) @@ -1456,7 +1461,7 @@ def merge(self, merge_commit_message=None, then merge Returns: - ProjectMergeRequet: The updated MR + ProjectMergeRequest: The updated MR Raises: GitlabConnectionError: If the server cannot be reached. GitlabMRForbiddenError: If the user doesn't have permission to From 178bfb77dd33ec9a434871c7b9b34ae320bd1ce4 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:40:50 +0200 Subject: [PATCH 81/85] Handle empty messages from server in exceptions --- gitlab/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/exceptions.py b/gitlab/exceptions.py index 3fb0613e5..41dad980c 100644 --- a/gitlab/exceptions.py +++ b/gitlab/exceptions.py @@ -134,7 +134,7 @@ class to raise. Should be inherited from GitLabError try: message = response.json()['message'] - except (KeyError, ValueError): + except (KeyError, ValueError, TypeError): message = response.content if isinstance(error, dict): From d7967c6d0d6621faf2ce294073f04b53172877d6 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:45:43 +0200 Subject: [PATCH 82/85] MR (un)subscribe: don't fail if state doesn't change --- gitlab/objects.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index d46d4fb1d..3ed6598ce 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1370,7 +1370,8 @@ def subscribe(self, **kwargs): r = self.gitlab._raw_post(url, **kwargs) raise_error_from_response(r, GitlabSubscribeError, [201, 304]) - self._set_from_dict(r.json()) + if r.status_code == 201: + self._set_from_dict(r.json()) def unsubscribe(self, **kwargs): """Unsubscribe a MR. @@ -1385,7 +1386,8 @@ def unsubscribe(self, **kwargs): r = self.gitlab._raw_delete(url, **kwargs) raise_error_from_response(r, GitlabUnsubscribeError, [200, 304]) - self._set_from_dict(r.json()) + if r.status_code == 200: + self._set_from_dict(r.json()) def cancel_merge_when_build_succeeds(self, **kwargs): """Cancel merge when build succeeds.""" From 799b5934d00c8ae199c5b0a6bdd18f4b0e06d223 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:47:07 +0200 Subject: [PATCH 83/85] MR merge(): update the object --- gitlab/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/objects.py b/gitlab/objects.py index 3ed6598ce..fe7c3791d 100644 --- a/gitlab/objects.py +++ b/gitlab/objects.py @@ -1484,7 +1484,7 @@ def merge(self, merge_commit_message=None, errors = {401: GitlabMRForbiddenError, 405: GitlabMRClosedError} raise_error_from_response(r, errors) - return ProjectMergeRequest(self, r.json()) + self._set_from_dict(r.json()) class ProjectMergeRequestManager(BaseManager): From 5614a7c9bf62aede3804469b6781f45d927508ea Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 21:49:44 +0200 Subject: [PATCH 84/85] docs: add MR API --- docs/api-objects.rst | 1 + docs/gl_objects/mrs.py | 61 +++++++++++++++++++++++++++++ docs/gl_objects/mrs.rst | 85 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 docs/gl_objects/mrs.py create mode 100644 docs/gl_objects/mrs.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index bf1a4a6e9..83aaa2064 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -14,6 +14,7 @@ API objects manipulation gl_objects/issues gl_objects/labels gl_objects/licenses + gl_objects/mrs gl_objects/namespaces gl_objects/projects gl_objects/runners diff --git a/docs/gl_objects/mrs.py b/docs/gl_objects/mrs.py new file mode 100644 index 000000000..130992327 --- /dev/null +++ b/docs/gl_objects/mrs.py @@ -0,0 +1,61 @@ +# list +mrs = gl.project_mergerequests.list(project_id=1) +# or +mrs = project.mergerequests.list() +# end list + +# filtered list +mrs = project.mergerequests.list(state='merged', order_by='updated_at') +# end filtered list + +# get +mr = gl.project_mergerequests.get(mr_id, project_id=1) +# or +mr = project.mergerequests.get(mr_id) +# end get + +# create +mr = gl.project_mergerequests.create({'source_branch': 'cool_feature', + 'target_branch': 'master', + 'title': 'merge cool feature'}, + project_id=1) +# or +mr = project.mergerequests.create({'source_branch': 'cool_feature', + 'target_branch': 'master', + 'title': 'merge cool feature'}) +# end create + +# update +mr.description = 'New description' +mr.save() +# end update + +# state +mr.state_event = 'close' # or 'reopen' +mr.save() +# end state + +# delete +gl.project_mergerequests.delete(mr_id, project_id=1) +# or +project.mergerequests.delete(mr_id) +# or +mr.delete() +# end delete + +# merge +mr.merge() +# end merge + +# cancel +mr.cancel_merge_when_build_succeeds() +# end cancel + +# issues +mr.closes_issues() +# end issues + +# subscribe +mr.subscribe() +mr.unsubscribe() +# end subscribe diff --git a/docs/gl_objects/mrs.rst b/docs/gl_objects/mrs.rst new file mode 100644 index 000000000..2def079e9 --- /dev/null +++ b/docs/gl_objects/mrs.rst @@ -0,0 +1,85 @@ +############## +Merge requests +############## + +Use :class:`~gitlab.objects.ProjectMergeRequest` objects to manipulate MRs for +projects. The :attr:`gitlab.Gitlab.project_mergerequests` and +:attr:`Project.mergerequests ` manager +objects provide helper functions. + +Examples +-------- + +List MRs for a project: + +.. literalinclude:: mrs.py + :start-after: # list + :end-before: # end list + +You can filter and sort the returned list with the following parameters: + +* ``iid``: iid (unique ID for the project) of the MR +* ``state``: state of the MR. It can be one of ``all``, ``merged``, '``opened`` + or ``closed`` +* ``order_by``: sort by ``created_at`` or ``updated_at`` +* ``sort``: sort order (``asc`` or ``desc``) + +For example: + +.. literalinclude:: mrs.py + :start-after: # list + :end-before: # end list + +Get a single MR: + +.. literalinclude:: mrs.py + :start-after: # get + :end-before: # end get + +Create a MR: + +.. literalinclude:: mrs.py + :start-after: # create + :end-before: # end create + +Update a MR: + +.. literalinclude:: mrs.py + :start-after: # update + :end-before: # end update + +Change the state of a MR (close or reopen): + +.. literalinclude:: mrs.py + :start-after: # state + :end-before: # end state + +Delete a MR: + +.. literalinclude:: mrs.py + :start-after: # delete + :end-before: # end delete + +Accept a MR: + +.. literalinclude:: mrs.py + :start-after: # merge + :end-before: # end merge + +Cancel a MR when the build succeeds: + +.. literalinclude:: mrs.py + :start-after: # cancel + :end-before: # end cancel + +List issues that will close on merge: + +.. literalinclude:: mrs.py + :start-after: # issues + :end-before: # end issues + +Subscribe/unsubscribe a MR: + +.. literalinclude:: mrs.py + :start-after: # subscribe + :end-before: # end subscribe From e4624c9f17a8fbbe57da4316e6927f6d39bcc5a3 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 7 Aug 2016 22:11:36 +0200 Subject: [PATCH 85/85] Update changelog/authors/version for 0.14 --- AUTHORS | 7 +++++++ ChangeLog | 51 ++++++++++++++++++++++++++++++++++++++++++++++ gitlab/__init__.py | 2 +- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index dc45ad59b..cf419ddff 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,3 +29,10 @@ Mikhail Lopotkov Asher256 Adam Reid Guyzmo +Christian Wenk +Kris Gambirazzi +Ivica Arsov +Peter Mosmans +Stefan K. Dunkler +Missionrulz +Rafael Eyng diff --git a/ChangeLog b/ChangeLog index 392a081d6..0baeb35f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,54 @@ +Version 0.14 + + * Remove 'next_url' from kwargs before passing it to the cls constructor. + * List projects under group + * Add support for subscribe and unsubscribe in issues + * Project issue: doc and CLI for (un)subscribe + * Added support for HTTP basic authentication + * Add support for build artifacts and trace + * --title is a required argument for ProjectMilestone + * Commit status: add optional context url + * Commit status: optional get attrs + * Add support for commit comments + * Issues: add optional listing parameters + * Issues: add missing optional listing parameters + * Project issue: proper update attributes + * Add support for project-issue move + * Update ProjectLabel attributes + * Milestone: optional listing attrs + * Add support for namespaces + * Add support for label (un)subscribe + * MR: add (un)subscribe support + * Add `note_events` to project hooks attributes + * Add code examples for a bunch of resources + * Implement user emails support + * Project: add VISIBILITY_* constants + * Fix the Project.archive call + * Implement archive/unarchive for a projet + * Update ProjectSnippet attributes + * Fix ProjectMember update + * Implement sharing project with a group + * Implement CLI for project archive/unarchive/share + * Implement runners global API + * Gitlab: add managers for build-related resources + * Implement ProjectBuild.keep_artifacts + * Allow to stream the downloads when appropriate + * Groups can be updated + * Replace Snippet.Content() with a new content() method + * CLI: refactor _die() + * Improve commit statuses and comments + * Add support from listing group issues + * Added a new project attribute to enable the container registry. + * Add a contributing section in README + * Add support for global deploy key listing + * Add support for project environments + * MR: get list of changes and commits + * Fix the listing of some resources + * MR: fix updates + * Handle empty messages from server in exceptions + * MR (un)subscribe: don't fail if state doesn't change + * MR merge(): update the object + Version 0.13 * Add support for MergeRequest validation diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 15a142ca7..fda330435 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -32,7 +32,7 @@ from gitlab.objects import * # noqa __title__ = 'python-gitlab' -__version__ = '0.13' +__version__ = '0.14' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3'