From 34e32a0944b65583a57b97bf0124b8935ab49fa7 Mon Sep 17 00:00:00 2001 From: Moritz Lipp Date: Mon, 13 Nov 2017 15:12:36 +0100 Subject: [PATCH 01/25] Project pipeline schedules --- gitlab/v3/objects.py | 13 ++++++++++ gitlab/v4/objects.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/gitlab/v3/objects.py b/gitlab/v3/objects.py index ab815215f..014714e62 100644 --- a/gitlab/v3/objects.py +++ b/gitlab/v3/objects.py @@ -1496,6 +1496,18 @@ class ProjectFileManager(BaseManager): obj_cls = ProjectFile +class ProjectPipelineSchedule(GitlabObject): + _url = '/projects/%(project_id)s/pipeline_schedules' + _create_url = '/projects/%(project_id)s/pipeline_schedules' + + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['description', 'ref', 'cron'] + + +class ProjectPipelineSchedulesManager(BaseManager): + obj_cls = ProjectPipelineSchedule + + class ProjectPipeline(GitlabObject): _url = '/projects/%(project_id)s/pipelines' _create_url = '/projects/%(project_id)s/pipeline' @@ -1803,6 +1815,7 @@ class Project(GitlabObject): ('notificationsettings', 'ProjectNotificationSettingsManager', [('project_id', 'id')]), ('pipelines', 'ProjectPipelineManager', [('project_id', 'id')]), + ('pipeline_schedules', 'ProjectPipelineSchedulesManager', [('project_id', 'id')]), ('runners', 'ProjectRunnerManager', [('project_id', 'id')]), ('services', 'ProjectServiceManager', [('project_id', 'id')]), ('snippets', 'ProjectSnippetManager', [('project_id', 'id')]), diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 85aba126e..6a538e12b 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1764,6 +1764,65 @@ def create(self, data, **kwargs): return CreateMixin.create(self, data, path=path, **kwargs) +class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = 'key' + + +class ProjectPipelineScheduleVariableManager(CRUDMixin, RESTManager): + _path = '/projects/%(project_id)s/pipeline_schedules/%(pipeline_schedule_id)s/variables' + _obj_cls = ProjectPipelineScheduleVariable + _from_parent_attrs = {'project_id': 'project_id', + 'pipeline_schedule_id' : 'id'} + _create_attrs = (('pipeline_schedule_id', 'key', 'value'), tuple()) + _create_attrs = (('key', 'value'), tuple()) + + def list(self): + array = [] + if 'variables' in self._parent._attrs: + for variable in self._parent._attrs['variables']: + schedule_variable = self._obj_cls(self, variable) + array.append(schedule_variable) + else: + obj = self._parent.manager.get(self._parent.id) + for variable in obj._attrs['variables']: + schedule_variable = self._obj_cls(self, variable) + array.append(schedule_variable) + + return array + + +class ProjectPipelineSchedule(RESTObject): + _managers = ( + ('variables', 'ProjectPipelineScheduleVariableManager'), + ) + + +class ProjectPipelineSchedulesManager(RetrieveMixin, CreateMixin, RESTManager): + _path = '/projects/%(project_id)s/pipeline_schedules' + _obj_cls = ProjectPipelineSchedule + _from_parent_attrs = {'project_id': 'id'} + _create_attrs = (('description', 'ref', 'cron'), + ('cron_timezone', 'active')) + + def create(self, data, **kwargs): + """Creates a new object. + + Args: + data (dict): Parameters to send to the server to create the + resource + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + + Returns: + RESTObject: A new instance of the managed object class build with + the data sent by the server + """ + return CreateMixin.create(self, data, path=self.path, **kwargs) + + class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -2035,6 +2094,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ('notificationsettings', 'ProjectNotificationSettingsManager'), ('pipelines', 'ProjectPipelineManager'), ('protectedbranches', 'ProjectProtectedBranchManager'), + ('pipeline_schedules', 'ProjectPipelineSchedulesManager'), ('runners', 'ProjectRunnerManager'), ('services', 'ProjectServiceManager'), ('snippets', 'ProjectSnippetManager'), From b861837b25bb45dbe40b035dff5f41898450e22b Mon Sep 17 00:00:00 2001 From: Moritz Lipp Date: Fri, 13 Oct 2017 14:17:40 +0200 Subject: [PATCH 02/25] Project pipeline jobs --- gitlab/v4/objects.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 6a538e12b..17e987c2d 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1706,7 +1706,23 @@ def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024, return utils.response_content(result, streamed, action, chunk_size) +class ProjectPipelineJob(ProjectJob): + pass + + +class ProjectPipelineJobsManager(ListMixin, RESTManager): + _path = '/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs' + _obj_cls = ProjectPipelineJob + _from_parent_attrs = {'project_id': 'project_id', + 'pipeline_id' : 'id'} + _list_filters = ('scope',) + + class ProjectPipeline(RESTObject): + _managers = ( + ('jobs', 'ProjectPipelineJobsManager'), + ) + @cli.register_custom_action('ProjectPipeline') @exc.on_http_error(exc.GitlabPipelineCancelError) def cancel(self, **kwargs): From 9253661c381e9298643e689074c00b7fae831955 Mon Sep 17 00:00:00 2001 From: Michael Overmeyer Date: Fri, 5 Jan 2018 18:19:26 +0000 Subject: [PATCH 03/25] Adding the supported version badge --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index cce2ad0e3..652b79f8e 100644 --- a/README.rst +++ b/README.rst @@ -7,6 +7,9 @@ .. image:: https://readthedocs.org/projects/python-gitlab/badge/?version=latest :target: https://python-gitlab.readthedocs.org/en/latest/?badge=latest +.. image:: https://img.shields.io/pypi/pyversions/python-gitlab.svg + :target: https://pypi.python.org/pypi/python-gitlab + Python GitLab ============= From b980c9f7db97f8d55ed50d116a1d9fcf817ebf0d Mon Sep 17 00:00:00 2001 From: Michael Overmeyer Date: Fri, 5 Jan 2018 18:23:20 +0000 Subject: [PATCH 04/25] Clarifying what supports means --- docs/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.rst b/docs/install.rst index 1bc6d1706..499832072 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -2,7 +2,7 @@ Installation ############ -``python-gitlab`` is compatible with python 2 and 3. +``python-gitlab`` is compatible with Python 2.7 and 3.4+. Use :command:`pip` to install the latest stable version of ``python-gitlab``: From bdb6d63d4f7423e80e51350546698764994f08c8 Mon Sep 17 00:00:00 2001 From: Keith Wansbrough Date: Thu, 18 Jan 2018 06:13:24 +0000 Subject: [PATCH 05/25] Add manager for jobs within a pipeline. (#413) --- docs/gl_objects/builds.py | 12 ++++++++++-- docs/gl_objects/builds.rst | 18 ++++++++++++++---- gitlab/v4/objects.py | 12 ++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/gl_objects/builds.py b/docs/gl_objects/builds.py index ba4b22bff..a5d20059a 100644 --- a/docs/gl_objects/builds.py +++ b/docs/gl_objects/builds.py @@ -55,10 +55,18 @@ builds = commit.builds() # end commit list -# get +# pipeline list get +# v4 only +project = gl.projects.get(project_id) +pipeline = project.pipelines.get(pipeline_id) +jobs = pipeline.jobs.list() # gets all jobs in pipeline +job = pipeline.jobs.get(job_id) # gets one job from pipeline +# end pipeline list get + +# get job project.builds.get(build_id) # v3 project.jobs.get(job_id) # v4 -# end get +# end get job # artifacts build_or_job.artifacts() diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index 1c95eb16e..b0f3e22f0 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -122,8 +122,9 @@ Remove a variable: Builds/Jobs =========== -Builds/Jobs are associated to projects and commits. They provide information on -the builds/jobs that have been run, and methods to manipulate them. +Builds/Jobs are associated to projects, pipelines and commits. They provide +information on the builds/jobs that have been run, and methods to manipulate +them. Reference --------- @@ -169,11 +170,20 @@ To list builds for a specific commit, create a :start-after: # commit list :end-before: # end commit list +To list builds for a specific pipeline or get a single job within a specific +pipeline, create a +:class:`~gitlab.v4.objects.ProjectPipeline` object and use its +:attr:`~gitlab.v4.objects.ProjectPipeline.jobs` method (v4 only): + +.. literalinclude:: builds.py + :start-after: # pipeline list get + :end-before: # end pipeline list get + Get a job: .. literalinclude:: builds.py - :start-after: # get - :end-before: # end get + :start-after: # get job + :end-before: # end get job Get a job artifact: diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index d7bb3d590..e4a544730 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1883,6 +1883,8 @@ def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024, class ProjectPipeline(RESTObject): + _managers = (('jobs', 'ProjectPipelineJobManager'), ) + @cli.register_custom_action('ProjectPipeline') @exc.on_http_error(exc.GitlabPipelineCancelError) def cancel(self, **kwargs): @@ -1940,6 +1942,16 @@ def create(self, data, **kwargs): return CreateMixin.create(self, data, path=path, **kwargs) +class ProjectPipelineJob(ProjectJob): + pass + + +class ProjectPipelineJobManager(GetFromListMixin, RESTManager): + _path = '/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs' + _obj_cls = ProjectPipelineJob + _from_parent_attrs = {'project_id': 'project_id', 'pipeline_id': 'id'} + + class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass From e95781720210b62753af4463dd6c2e5f106439c8 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 20 Jan 2018 12:59:52 +0100 Subject: [PATCH 06/25] Fix wrong tag example Fixes #416 --- docs/gl_objects/projects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 1790cc825..7ec23593d 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -229,7 +229,7 @@ # end tags list # tags get -tags = project.tags.list('1.0') +tag = project.tags.get('1.0') # end tags get # tags create From 638da6946d0a731aee3392b9eafc610985691855 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 20 Jan 2018 13:19:21 +0100 Subject: [PATCH 07/25] Update the groups documentation Closes #410 --- docs/gl_objects/groups.py | 50 ------------------------ docs/gl_objects/groups.rst | 79 +++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 94 deletions(-) delete mode 100644 docs/gl_objects/groups.py diff --git a/docs/gl_objects/groups.py b/docs/gl_objects/groups.py deleted file mode 100644 index f1a2a8f60..000000000 --- a/docs/gl_objects/groups.py +++ /dev/null @@ -1,50 +0,0 @@ -# list -groups = gl.groups.list() -# end list - -# get -group = gl.groups.get(group_id) -# end get - -# projects list -projects = group.projects.list() -# 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 = group.members.list() -# end member list - -# member get -members = group.members.get(member_id) -# end member get - -# member create -member = group.members.create({'user_id': user_id, - 'access_level': gitlab.GUEST_ACCESS}) -# end member create - -# member update -member.access_level = gitlab.DEVELOPER_ACCESS -member.save() -# end member update - -# member delete -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 index 5536de2ca..493f5d0ba 100644 --- a/docs/gl_objects/groups.rst +++ b/docs/gl_objects/groups.rst @@ -25,23 +25,17 @@ Reference Examples -------- -List the groups: +List the groups:: -.. literalinclude:: groups.py - :start-after: # list - :end-before: # end list + groups = gl.groups.list() -Get a group's detail: +Get a group's detail:: -.. literalinclude:: groups.py - :start-after: # get - :end-before: # end get + group = gl.groups.get(group_id) -List a group's projects: +List a group's projects:: -.. literalinclude:: groups.py - :start-after: # projects list - :end-before: # end projects list + projects = group.projects.list() You can filter and sort the result using the following parameters: @@ -54,23 +48,20 @@ You can filter and sort the result using the following parameters: * ``sort``: sort order: ``asc`` or ``desc`` * ``ci_enabled_first``: return CI enabled groups first -Create a group: +Create a group:: -.. literalinclude:: groups.py - :start-after: # create - :end-before: # end create + group = gl.groups.create({'name': 'group1', 'path': 'group1'}) -Update a group: +Update a group:: -.. literalinclude:: groups.py - :start-after: # update - :end-before: # end update + group.description = 'My awesome group' + group.save() -Remove a group: +Remove a group:: -.. literalinclude:: groups.py - :start-after: # delete - :end-before: # end delete + gl.group.delete(group_id) + # or + group.delete() Subgroups ========= @@ -91,6 +82,12 @@ List the subgroups for a group:: subgroups = group.subgroups.list() + # The GroupSubgroup objects don't expose the same API as the Group + # objects. If you need to manipulate a subgroup as a group, create a new + # Group object: + real_group = gl.groups.get(subgroup_id, lazy=True) + real_group.issues.list() + Group custom attributes ======================= @@ -164,32 +161,26 @@ Reference Examples -------- -List group members: +List group members:: -.. literalinclude:: groups.py - :start-after: # member list - :end-before: # end member list + members = group.members.list() -Get a group member: +Get a group member:: -.. literalinclude:: groups.py - :start-after: # member get - :end-before: # end member get + members = group.members.get(member_id) -Add a member to the group: +Add a member to the group:: -.. literalinclude:: groups.py - :start-after: # member create - :end-before: # end member create + member = group.members.create({'user_id': user_id, + 'access_level': gitlab.GUEST_ACCESS}) -Update a member (change the access level): +Update a member (change the access level):: -.. literalinclude:: groups.py - :start-after: # member update - :end-before: # end member update + member.access_level = gitlab.DEVELOPER_ACCESS + member.save() -Remove a member from the group: +Remove a member from the group:: -.. literalinclude:: groups.py - :start-after: # member delete - :end-before: # end member delete + group.members.delete(member_id) + # or + member.delete() From 08f19b3d79dd50bab5afe58fe1b3b3825ddf9c25 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sat, 20 Jan 2018 13:43:23 +0100 Subject: [PATCH 08/25] Add support for MR participants API Fixes #387 --- gitlab/v4/objects.py | 24 ++++++++++++++++++++++++ tools/python_test_v4.py | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index e4a544730..211527da1 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1557,6 +1557,30 @@ def merge(self, merge_commit_message=None, **kwargs) self._update_attrs(server_data) + @cli.register_custom_action('ProjectMergeRequest') + @exc.on_http_error(exc.GitlabListError) + def participants(self, **kwargs): + """List the merge request participants. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the list could not be retrieved + + Returns: + RESTObjectList: The list of participants + """ + + path = '%s/%s/participants' % (self.manager.path, self.get_id()) + return self.manager.gitlab.http_get(path, **kwargs) + class ProjectMergeRequestManager(CRUDMixin, RESTManager): _path = '/projects/%(project_id)s/merge_requests' diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index e06502018..69596b8f1 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -527,6 +527,12 @@ mr = admin_project.mergerequests.create({'source_branch': 'branch1', 'target_branch': 'master', 'title': 'MR readme2'}) + +# basic testing: only make sure that the methods exist +mr.commits() +mr.changes() +#mr.participants() # not yet available + mr.merge() admin_project.branches.delete('branch1') From 96a1a784bd0cc0d0ce9dc3a83ea3a46380adc905 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 21 Jan 2018 11:42:12 +0100 Subject: [PATCH 09/25] Add support for getting list of user projects Fixes #403 --- docs/gl_objects/projects.py | 1 + gitlab/v4/objects.py | 27 ++++++++++++++++++++++++++- tools/python_test_v4.py | 3 +++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 7ec23593d..425bbe259 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -33,6 +33,7 @@ # user create alice = gl.users.list(username='alice')[0] user_project = alice.projects.create({'name': 'project'}) +user_projects = alice.projects.list() # end user create # update diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 211527da1..a5b603c0c 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -181,7 +181,7 @@ class UserProject(RESTObject): pass -class UserProjectManager(CreateMixin, RESTManager): +class UserProjectManager(ListMixin, CreateMixin, RESTManager): _path = '/projects/user/%(user_id)s' _obj_cls = UserProject _from_parent_attrs = {'user_id': 'id'} @@ -192,6 +192,31 @@ class UserProjectManager(CreateMixin, RESTManager): 'public', 'visibility', 'description', 'builds_enabled', 'public_builds', 'import_url', 'only_allow_merge_if_build_succeeds') ) + _list_filters = ('archived', 'visibility', 'order_by', 'sort', 'search', + 'simple', 'owned', 'membership', 'starred', 'statistics', + 'with_issues_enabled', 'with_merge_requests_enabled') + + def list(self, **kwargs): + """Retrieve a list of objects. + + Args: + all (bool): If True, return all the items, without pagination + per_page (int): Number of items to retrieve per request + page (int): ID of the page to return (starts with page 1) + as_list (bool): If set to False and no pagination option is + defined, return a generator instead of a list + **kwargs: Extra options to send to the Gitlab server (e.g. sudo) + + Returns: + list: The list of objects, or a generator if `as_list` is False + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabListError: If the server cannot perform the request + """ + + path = '/users/%s/projects' % self._parent.id + return ListMixin.list(self, path=path, **kwargs) class User(SaveMixin, ObjectDeleteMixin, RESTObject): diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 69596b8f1..e5d390a38 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -92,6 +92,9 @@ new_user.block() new_user.unblock() +# user projects list +assert(len(new_user.projects.list()) == 0) + foobar_user = gl.users.create( {'email': 'foobar@example.com', 'username': 'foobar', 'name': 'Foo Bar', 'password': 'foobar_password'}) From 1ca30807566ca3ac1bd295516a122cd75ba9031f Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 21 Jan 2018 18:30:38 +0100 Subject: [PATCH 10/25] Add Gitlab and User events support Closes #412 --- docs/api-objects.rst | 1 + docs/gl_objects/events.rst | 48 ++++++++++++++++++++++++++++++++++++ docs/gl_objects/projects.py | 6 ----- docs/gl_objects/projects.rst | 30 ---------------------- gitlab/__init__.py | 1 + gitlab/v4/objects.py | 29 +++++++++++++++++++--- tools/python_test_v4.py | 8 +++++- 7 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 docs/gl_objects/events.rst diff --git a/docs/api-objects.rst b/docs/api-objects.rst index 6879856b5..f2e72e20c 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -15,6 +15,7 @@ API examples gl_objects/deploy_keys gl_objects/deployments gl_objects/environments + gl_objects/events gl_objects/features gl_objects/groups gl_objects/issues diff --git a/docs/gl_objects/events.rst b/docs/gl_objects/events.rst new file mode 100644 index 000000000..807dcad4b --- /dev/null +++ b/docs/gl_objects/events.rst @@ -0,0 +1,48 @@ +###### +Events +###### + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.Event` + + :class:`gitlab.v4.objects.EventManager` + + :attr:`gitlab.Gitlab.events` + + :class:`gitlab.v4.objects.ProjectEvent` + + :class:`gitlab.v4.objects.ProjectEventManager` + + :attr:`gitlab.v4.objects.Project.events` + + :class:`gitlab.v4.objects.UserEvent` + + :class:`gitlab.v4.objects.UserEventManager` + + :attr:`gitlab.v4.objects.User.events` + +* v3 API (projects events only): + + + :class:`gitlab.v3.objects.ProjectEvent` + + :class:`gitlab.v3.objects.ProjectEventManager` + + :attr:`gitlab.v3.objects.Project.events` + + :attr:`gitlab.Gitlab.project_events` + +* GitLab API: https://docs.gitlab.com/ce/api/events.html + +Examples +-------- + +You can list events for an entire Gitlab instance (admin), users and projects. +You can filter you events you want to retrieve using the ``action`` and +``target_type`` attributes. The possibole values for these attributes are +available on `the gitlab documentation +`_. + +List all the events (paginated):: + + events = gl.events.list() + +List the issue events on a project:: + + events = project.events.list(target_type='issue') + +List the user events:: + + events = project.events.list() diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index 425bbe259..a633ee827 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -69,12 +69,6 @@ project.unarchive() # end archive -# events list -gl.project_events.list(project_id=1) -# or -project.events.list() -# end events list - # members list members = project.members.list() # end members list diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 03959502d..0c556f451 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -484,36 +484,6 @@ Delete a note for a resource: :start-after: # notes delete :end-before: # end notes delete -Project events -============== - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectEvent` - + :class:`gitlab.v4.objects.ProjectEventManager` - + :attr:`gitlab.v4.objects.Project.events` - -* v3 API: - - + :class:`gitlab.v3.objects.ProjectEvent` - + :class:`gitlab.v3.objects.ProjectEventManager` - + :attr:`gitlab.v3.objects.Project.events` - + :attr:`gitlab.Gitlab.project_events` - -* GitLab API: https://docs.gitlab.com/ce/api/repository_files.html - -Examples --------- - -List the project events: - -.. literalinclude:: projects.py - :start-after: # events list - :end-before: # end events list - Project members =============== diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 846380f5b..8a31a484b 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -125,6 +125,7 @@ def __init__(self, url, private_token=None, oauth_token=None, email=None, self.teams = objects.TeamManager(self) else: self.dockerfiles = objects.DockerfileManager(self) + self.events = objects.EventManager(self) self.features = objects.FeatureManager(self) self.pagesdomains = objects.PagesDomainManager(self) self.user_activities = objects.UserActivitiesManager(self) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index a5b603c0c..f8b0dce7f 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -112,6 +112,17 @@ def compound_metrics(self, **kwargs): return self.gitlab.http_get('/sidekiq/compound_metrics', **kwargs) +class Event(RESTObject): + _id_attr = None + _short_print_attr = 'target_title' + + +class EventManager(ListMixin, RESTManager): + _path = '/events' + _obj_cls = Event + _list_filters = ('action', 'target_type', 'before', 'after', 'sort') + + class UserActivities(RESTObject): _id_attr = 'username' @@ -143,6 +154,16 @@ class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): _create_attrs = (('email', ), tuple()) +class UserEvent(Event): + pass + + +class UserEventManager(EventManager): + _path = '/users/%(user_id)s/events' + _obj_cls = UserEvent + _from_parent_attrs = {'user_id': 'id'} + + class UserGPGKey(ObjectDeleteMixin, RESTObject): pass @@ -224,6 +245,7 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject): _managers = ( ('customattributes', 'UserCustomAttributeManager'), ('emails', 'UserEmailManager'), + ('events', 'UserEventManager'), ('gpgkeys', 'UserGPGKeyManager'), ('impersonationtokens', 'UserImpersonationTokenManager'), ('keys', 'UserKeyManager'), @@ -1161,12 +1183,11 @@ def enable(self, key_id, **kwargs): self.gitlab.http_post(path, **kwargs) -class ProjectEvent(RESTObject): - _id_attr = None - _short_print_attr = 'target_title' +class ProjectEvent(Event): + pass -class ProjectEventManager(ListMixin, RESTManager): +class ProjectEventManager(EventManager): _path = '/projects/%(project_id)s/events' _obj_cls = ProjectEvent _from_parent_attrs = {'project_id': 'id'} diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index e5d390a38..695722f9c 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -95,6 +95,9 @@ # user projects list assert(len(new_user.projects.list()) == 0) +# events list +new_user.events.list() + foobar_user = gl.users.create( {'email': 'foobar@example.com', 'username': 'foobar', 'name': 'Foo Bar', 'password': 'foobar_password'}) @@ -408,7 +411,7 @@ env.delete() assert(len(admin_project.environments.list()) == 0) -# events +# project events admin_project.events.list() # forks @@ -640,3 +643,6 @@ # user activities gl.user_activities.list() + +# events +gl.events.list() From 72ade19046f47b35c1b5ad7333f11fee0dc1e56f Mon Sep 17 00:00:00 2001 From: Pierre Tardy Date: Mon, 29 Jan 2018 17:44:22 +0100 Subject: [PATCH 11/25] make trigger_pipeline return the pipeline Trigger_pipeline returns nothing, which makes it difficult to track the pipeline being trigger. Next PR will be about updating a pipeline object to get latest status (not sure yet the best way to do it) --- gitlab/v4/objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index f8b0dce7f..67f80d061 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2624,7 +2624,8 @@ def trigger_pipeline(self, ref, token, variables={}, **kwargs): """ path = '/projects/%s/trigger/pipeline' % self.get_id() post_data = {'ref': ref, 'token': token, 'variables': variables} - self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) + attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) + return ProjectPipeline(project.pipelines, attrs) @cli.register_custom_action('Project') @exc.on_http_error(exc.GitlabHousekeepingError) From 29bd81336828b72a47673c76862cb4b532401766 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 4 Feb 2018 07:02:55 +0100 Subject: [PATCH 12/25] config: support api_version in the global section Fixes #421 --- docs/cli.rst | 4 +++- gitlab/config.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index f75a46a06..76203492d 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -37,7 +37,6 @@ example: default = somewhere ssl_verify = true timeout = 5 - api_version = 3 [somewhere] url = https://some.whe.re @@ -69,6 +68,9 @@ parameters. You can override the values in each GitLab server section. * - ``timeout`` - Integer - Number of seconds to wait for an answer before failing. + * - ``api_version`` + - ``3`` ou ``4`` + - The API version to use to make queries. Requires python-gitlab >= 1.3.0. You must define the ``url`` in each GitLab server section. diff --git a/gitlab/config.py b/gitlab/config.py index 9cf208c43..3166ec404 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -129,6 +129,10 @@ def __init__(self, gitlab_id=None, config_files=None): pass self.api_version = '3' + try: + self.api_version = self._config.get('global', 'api_version') + except Exception: + pass try: self.api_version = self._config.get(self.gitlab_id, 'api_version') except Exception: From b4f03173f33ed8d214ddc20b4791ec11677f6bb1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 5 Feb 2018 14:21:14 +0100 Subject: [PATCH 13/25] Gitlab can be used as context manager Fixes #371 --- RELEASE_NOTES.rst | 6 ++++++ docs/api-usage.rst | 17 +++++++++++++++++ gitlab/__init__.py | 6 ++++++ 3 files changed, 29 insertions(+) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 707b90d5f..da2545fe7 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -4,6 +4,12 @@ Release notes This page describes important changes between python-gitlab releases. +Changes from 1.2 to 1.3 +======================= + +* ``gitlab.Gitlab`` objects can be used as context managers in a ``with`` + block. + Changes from 1.1 to 1.2 ======================= diff --git a/docs/api-usage.rst b/docs/api-usage.rst index 3704591e8..5816b6d97 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -274,6 +274,23 @@ HTTP requests to the Gitlab servers. You can provide your own ``Session`` object with custom configuration when you create a ``Gitlab`` object. +Context manager +--------------- + +You can use ``Gitlab`` objects as context managers. This makes sure that the +``requests.Session`` object associated with a ``Gitlab`` instance is always +properly closed when you exit a ``with`` block: + +.. code-block:: python + + with gitlab.Gitlab(host, token) as gl: + gl.projects.list() + +.. warning:: + + The context manager will also close the custom ``Session`` object you might + have used to build a ``Gitlab`` instance. + Proxy configuration ------------------- diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 8a31a484b..69629f8cd 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -147,6 +147,12 @@ def __init__(self, url, private_token=None, oauth_token=None, email=None, manager = getattr(objects, cls_name)(self) setattr(self, var_name, manager) + def __enter__(self): + return self + + def __exit__(self, *args): + self.session.close() + def __getstate__(self): state = self.__dict__.copy() state.pop('_objects') From f276f13df50132554984f989b1d3d6c5fa8cdc01 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 5 Feb 2018 14:43:02 +0100 Subject: [PATCH 14/25] Default to API v4 --- docs/api-usage.rst | 12 ++++----- docs/cli.rst | 6 ++--- docs/switching-to-v4.rst | 8 +++--- gitlab/__init__.py | 2 +- gitlab/config.py | 2 +- gitlab/tests/test_gitlab.py | 6 ++--- gitlab/tests/test_gitlabobject.py | 44 +++++++++++++++---------------- gitlab/tests/test_manager.py | 3 ++- 8 files changed, 41 insertions(+), 42 deletions(-) diff --git a/docs/api-usage.rst b/docs/api-usage.rst index 5816b6d97..190482f6f 100644 --- a/docs/api-usage.rst +++ b/docs/api-usage.rst @@ -7,7 +7,7 @@ python-gitlab supports both GitLab v3 and v4 APIs. v3 being deprecated by GitLab, its support in python-gitlab will be minimal. The development team will focus on v4. -v3 is still the default API used by python-gitlab, for compatibility reasons. +v4 is the default API used by python-gitlab since version 1.3.0. ``gitlab.Gitlab`` class @@ -63,21 +63,19 @@ for a detailed discussion. API version =========== -``python-gitlab`` uses the v3 GitLab API by default. Use the ``api_version`` -parameter to switch to v4: +``python-gitlab`` uses the v4 GitLab API by default. Use the ``api_version`` +parameter to switch to v3: .. code-block:: python import gitlab - gl = gitlab.Gitlab('http://10.0.0.1', 'JVNSESs8EwWRx5yDxM5q', api_version=4) + gl = gitlab.Gitlab('http://10.0.0.1', 'JVNSESs8EwWRx5yDxM5q', api_version=3) .. warning:: The python-gitlab API is not the same for v3 and v4. Make sure to read - :ref:`switching_to_v4` before upgrading. - - v4 will become the default in python-gitlab. + :ref:`switching_to_v4` if you are upgrading from v3. Managers ======== diff --git a/docs/cli.rst b/docs/cli.rst index 76203492d..591761cae 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -41,7 +41,7 @@ example: [somewhere] url = https://some.whe.re private_token = vTbFeqJYCY3sibBP7BZM - api_version = 4 + api_version = 3 [elsewhere] url = http://else.whe.re:8080 @@ -92,8 +92,8 @@ limited permissions. - An Oauth token for authentication. The Gitlab server must be configured to support this authentication method. * - ``api_version`` - - GitLab API version to use (``3`` or ``4``). Defaults to ``3`` for now, - but will switch to ``4`` eventually. + - GitLab API version to use (``3`` or ``4``). Defaults to ``4`` since + version 1.3.0. * - ``http_username`` - Username for optional HTTP authentication * - ``http_password`` diff --git a/docs/switching-to-v4.rst b/docs/switching-to-v4.rst index 217463d9d..ef2106088 100644 --- a/docs/switching-to-v4.rst +++ b/docs/switching-to-v4.rst @@ -16,12 +16,12 @@ http://gitlab.com. Using the v4 API ================ -To use the new v4 API, explicitly define ``api_version` `in the ``Gitlab`` -constructor: +python-gitlab uses the v4 API by default since the 1.3.0 release. To use the +old v3 API, explicitly define ``api_version`` in the ``Gitlab`` constructor: .. code-block:: python - gl = gitlab.Gitlab(..., api_version=4) + gl = gitlab.Gitlab(..., api_version=3) If you use the configuration file, also explicitly define the version: @@ -30,7 +30,7 @@ If you use the configuration file, also explicitly define the version: [my_gitlab] ... - api_version = 4 + api_version = 3 Changes between v3 and v4 API diff --git a/gitlab/__init__.py b/gitlab/__init__.py index 69629f8cd..c909f9f06 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -73,7 +73,7 @@ class Gitlab(object): def __init__(self, url, private_token=None, oauth_token=None, email=None, password=None, ssl_verify=True, http_username=None, - http_password=None, timeout=None, api_version='3', + http_password=None, timeout=None, api_version='4', session=None): self._api_version = str(api_version) diff --git a/gitlab/config.py b/gitlab/config.py index 3166ec404..0f4c42439 100644 --- a/gitlab/config.py +++ b/gitlab/config.py @@ -128,7 +128,7 @@ def __init__(self, gitlab_id=None, config_files=None): except Exception: pass - self.api_version = '3' + self.api_version = '4' try: self.api_version = self._config.get('global', 'api_version') except Exception: diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index d33df9952..1a1f3d83f 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -53,7 +53,7 @@ class TestGitlabRawMethods(unittest.TestCase): def setUp(self): self.gl = Gitlab("http://localhost", private_token="private_token", email="testuser@test.com", password="testpassword", - ssl_verify=True) + ssl_verify=True, api_version=3) @urlmatch(scheme="http", netloc="localhost", path="/api/v3/known_path", method="get") @@ -454,7 +454,7 @@ class TestGitlabMethods(unittest.TestCase): def setUp(self): self.gl = Gitlab("http://localhost", private_token="private_token", email="testuser@test.com", password="testpassword", - ssl_verify=True) + ssl_verify=True, api_version=3) def test_list(self): @urlmatch(scheme="http", netloc="localhost", @@ -938,7 +938,7 @@ class TestGitlab(unittest.TestCase): def setUp(self): self.gl = Gitlab("http://localhost", private_token="private_token", email="testuser@test.com", password="testpassword", - ssl_verify=True) + ssl_verify=True, api_version=3) def test_pickability(self): original_gl_objects = self.gl._objects diff --git a/gitlab/tests/test_gitlabobject.py b/gitlab/tests/test_gitlabobject.py index f7fd1872f..844ba9e83 100644 --- a/gitlab/tests/test_gitlabobject.py +++ b/gitlab/tests/test_gitlabobject.py @@ -34,7 +34,7 @@ from gitlab import * # noqa -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/projects/1", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1", method="get") def resp_get_project(url, request): headers = {'content-type': 'application/json'} @@ -42,7 +42,7 @@ def resp_get_project(url, request): return response(200, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/projects", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="get") def resp_list_project(url, request): headers = {'content-type': 'application/json'} @@ -50,7 +50,7 @@ def resp_list_project(url, request): return response(200, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/issues/1", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/issues/1", method="get") def resp_get_issue(url, request): headers = {'content-type': 'application/json'} @@ -58,7 +58,7 @@ def resp_get_issue(url, request): return response(200, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/users/1", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/users/1", method="put") def resp_update_user(url, request): headers = {'content-type': 'application/json'} @@ -67,7 +67,7 @@ def resp_update_user(url, request): return response(200, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/projects", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects", method="post") def resp_create_project(url, request): headers = {'content-type': 'application/json'} @@ -75,7 +75,7 @@ def resp_create_project(url, request): return response(201, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/groups/2/members", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/2/members", method="post") def resp_create_groupmember(url, request): headers = {'content-type': 'application/json'} @@ -84,14 +84,14 @@ def resp_create_groupmember(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/snippets/3", method="get") + path="/api/v4/projects/2/snippets/3", method="get") def resp_get_projectsnippet(url, request): headers = {'content-type': 'application/json'} content = '{"title": "test", "id": 3}'.encode("utf-8") return response(200, content, headers, None, 5, request) -@urlmatch(scheme="http", netloc="localhost", path="/api/v3/groups/1", +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1", method="delete") def resp_delete_group(url, request): headers = {'content-type': 'application/json'} @@ -100,7 +100,7 @@ def resp_delete_group(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/groups/2/projects/3", + path="/api/v4/groups/2/projects/3", method="post") def resp_transfer_project(url, request): headers = {'content-type': 'application/json'} @@ -109,7 +109,7 @@ def resp_transfer_project(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/groups/2/projects/3", + path="/api/v4/groups/2/projects/3", method="post") def resp_transfer_project_fail(url, request): headers = {'content-type': 'application/json'} @@ -118,7 +118,7 @@ def resp_transfer_project_fail(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/branches/branchname/protect", + path="/api/v4/projects/2/repository/branches/branchname/protect", method="put") def resp_protect_branch(url, request): headers = {'content-type': 'application/json'} @@ -127,7 +127,7 @@ def resp_protect_branch(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/branches/branchname/unprotect", + path="/api/v4/projects/2/repository/branches/branchname/unprotect", method="put") def resp_unprotect_branch(url, request): headers = {'content-type': 'application/json'} @@ -136,7 +136,7 @@ def resp_unprotect_branch(url, request): @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/branches/branchname/protect", + path="/api/v4/projects/2/repository/branches/branchname/protect", method="put") def resp_protect_branch_fail(url, request): headers = {'content-type': 'application/json'} @@ -157,7 +157,7 @@ def test_json(self): data = json.loads(json_str) self.assertIn("id", data) self.assertEqual(data["username"], "testname") - self.assertEqual(data["gitlab"]["url"], "http://localhost/api/v3") + self.assertEqual(data["gitlab"]["url"], "http://localhost/api/v4") def test_pickability(self): gl_object = CurrentUser(self.gl, data={"username": "testname"}) @@ -381,7 +381,7 @@ def setUp(self): self.obj = ProjectCommit(self.gl, data={"id": 3, "project_id": 2}) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/commits/3/diff", + path="/api/v4/projects/2/repository/commits/3/diff", method="get") def resp_diff(self, url, request): headers = {'content-type': 'application/json'} @@ -389,7 +389,7 @@ def resp_diff(self, url, request): return response(200, content, headers, None, 5, request) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/commits/3/diff", + path="/api/v4/projects/2/repository/commits/3/diff", method="get") def resp_diff_fail(self, url, request): headers = {'content-type': 'application/json'} @@ -397,7 +397,7 @@ def resp_diff_fail(self, url, request): return response(400, content, headers, None, 5, request) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/blobs/3", + path="/api/v4/projects/2/repository/blobs/3", method="get") def resp_blob(self, url, request): headers = {'content-type': 'application/json'} @@ -405,7 +405,7 @@ def resp_blob(self, url, request): return response(200, content, headers, None, 5, request) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/repository/blobs/3", + path="/api/v4/projects/2/repository/blobs/3", method="get") def resp_blob_fail(self, url, request): headers = {'content-type': 'application/json'} @@ -440,7 +440,7 @@ def setUp(self): self.obj = ProjectSnippet(self.gl, data={"id": 3, "project_id": 2}) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/snippets/3/raw", + path="/api/v4/projects/2/snippets/3/raw", method="get") def resp_content(self, url, request): headers = {'content-type': 'application/json'} @@ -448,7 +448,7 @@ def resp_content(self, url, request): return response(200, content, headers, None, 5, request) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/projects/2/snippets/3/raw", + path="/api/v4/projects/2/snippets/3/raw", method="get") def resp_content_fail(self, url, request): headers = {'content-type': 'application/json'} @@ -474,7 +474,7 @@ def setUp(self): self.obj = Snippet(self.gl, data={"id": 3}) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/snippets/3/raw", + path="/api/v4/snippets/3/raw", method="get") def resp_content(self, url, request): headers = {'content-type': 'application/json'} @@ -482,7 +482,7 @@ def resp_content(self, url, request): return response(200, content, headers, None, 5, request) @urlmatch(scheme="http", netloc="localhost", - path="/api/v3/snippets/3/raw", + path="/api/v4/snippets/3/raw", method="get") def resp_content_fail(self, url, request): headers = {'content-type': 'application/json'} diff --git a/gitlab/tests/test_manager.py b/gitlab/tests/test_manager.py index 5cd3130d1..c6ef2992c 100644 --- a/gitlab/tests/test_manager.py +++ b/gitlab/tests/test_manager.py @@ -52,7 +52,8 @@ class TestGitlabManager(unittest.TestCase): def setUp(self): self.gitlab = Gitlab("http://localhost", private_token="private_token", email="testuser@test.com", - password="testpassword", ssl_verify=True) + password="testpassword", ssl_verify=True, + api_version=3) def test_set_parent_args(self): @urlmatch(scheme="http", netloc="localhost", path="/api/v3/fake", From faeb1c140637ce25209e5553cab6a488c363a912 Mon Sep 17 00:00:00 2001 From: Pierre Tardy Date: Mon, 29 Jan 2018 17:38:46 +0100 Subject: [PATCH 15/25] add a Simplified example for streamed artifacts Going through an object adds a lot of complication. Adding example for unzipping on the fly --- docs/gl_objects/builds.py | 12 ++++++++++-- docs/gl_objects/builds.rst | 10 ++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/gl_objects/builds.py b/docs/gl_objects/builds.py index a5d20059a..0f616e842 100644 --- a/docs/gl_objects/builds.py +++ b/docs/gl_objects/builds.py @@ -72,7 +72,7 @@ build_or_job.artifacts() # end artifacts -# stream artifacts +# stream artifacts with class class Foo(object): def __init__(self): self._fd = open('artifacts.zip', 'wb') @@ -83,7 +83,15 @@ def __call__(self, chunk): target = Foo() build_or_job.artifacts(streamed=True, action=target) del(target) # flushes data on disk -# end stream artifacts +# end stream artifacts with class + +# stream artifacts with unzip +zipfn = "___artifacts.zip" +with open(zipfn, "wb") as f: + build_or_job.artifacts(streamed=True, action=f.write) +subprocess.run(["unzip", "-bo", zipfn]) +os.unlink(zipfn) +# end stream artifacts with unzip # keep artifacts build_or_job.keep_artifacts() diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index b0f3e22f0..9d6873626 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -201,8 +201,14 @@ 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 + :start-after: # stream artifacts with class + :end-before: # end stream artifacts with class + +In this second example, you can directly stream the output into a file, and unzip it afterwards: + +.. literalinclude:: builds.py + :start-after: # stream artifacts with unzip + :end-before: # end stream artifacts with unzip Mark a job artifact as kept when expiration is set: From 8134f84f96059dbde72359c414352e2dbe3535e0 Mon Sep 17 00:00:00 2001 From: Pierre Tardy Date: Mon, 5 Feb 2018 14:58:53 +0100 Subject: [PATCH 16/25] fix pep8 --- gitlab/v4/objects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 67f80d061..7adf82cc7 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2624,8 +2624,9 @@ def trigger_pipeline(self, ref, token, variables={}, **kwargs): """ path = '/projects/%s/trigger/pipeline' % self.get_id() post_data = {'ref': ref, 'token': token, 'variables': variables} - attrs = self.manager.gitlab.http_post(path, post_data=post_data, **kwargs) - return ProjectPipeline(project.pipelines, attrs) + attrs = self.manager.gitlab.http_post( + path, post_data=post_data, **kwargs) + return ProjectPipeline(self.pipelines, attrs) @cli.register_custom_action('Project') @exc.on_http_error(exc.GitlabHousekeepingError) From 31eb913be34f8dea0c4b1f8396b74bb74b32a6f0 Mon Sep 17 00:00:00 2001 From: Moritz Lipp Date: Mon, 13 Nov 2017 15:12:36 +0100 Subject: [PATCH 17/25] Project pipeline schedules --- gitlab/v3/objects.py | 13 +++++++++ gitlab/v4/objects.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/gitlab/v3/objects.py b/gitlab/v3/objects.py index 0db9dfd6b..9ea597c44 100644 --- a/gitlab/v3/objects.py +++ b/gitlab/v3/objects.py @@ -1496,6 +1496,18 @@ class ProjectFileManager(BaseManager): obj_cls = ProjectFile +class ProjectPipelineSchedule(GitlabObject): + _url = '/projects/%(project_id)s/pipeline_schedules' + _create_url = '/projects/%(project_id)s/pipeline_schedules' + + requiredUrlAttrs = ['project_id'] + requiredCreateAttrs = ['description', 'ref', 'cron'] + + +class ProjectPipelineSchedulesManager(BaseManager): + obj_cls = ProjectPipelineSchedule + + class ProjectPipeline(GitlabObject): _url = '/projects/%(project_id)s/pipelines' _create_url = '/projects/%(project_id)s/pipeline' @@ -1803,6 +1815,7 @@ class Project(GitlabObject): ('notificationsettings', 'ProjectNotificationSettingsManager', [('project_id', 'id')]), ('pipelines', 'ProjectPipelineManager', [('project_id', 'id')]), + ('pipeline_schedules', 'ProjectPipelineSchedulesManager', [('project_id', 'id')]), ('runners', 'ProjectRunnerManager', [('project_id', 'id')]), ('services', 'ProjectServiceManager', [('project_id', 'id')]), ('snippets', 'ProjectSnippetManager', [('project_id', 'id')]), diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 7adf82cc7..ea35bc707 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2012,6 +2012,69 @@ def create(self, data, **kwargs): return CreateMixin.create(self, data, path=path, **kwargs) +class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): + _id_attr = 'key' + + +class ProjectPipelineScheduleVariableManager(CRUDMixin, RESTManager): + _path = '/projects/%(project_id)s/pipeline_schedules/%(pipeline_schedule_id)s/variables' + _obj_cls = ProjectPipelineScheduleVariable + _from_parent_attrs = {'project_id': 'project_id', + 'pipeline_schedule_id' : 'id'} + _create_attrs = (('pipeline_schedule_id', 'key', 'value'), tuple()) + _create_attrs = (('key', 'value'), tuple()) + + def list(self): + array = [] + if 'variables' in self._parent._attrs: + for variable in self._parent._attrs['variables']: + schedule_variable = self._obj_cls(self, variable) + array.append(schedule_variable) + else: + obj = self._parent.manager.get(self._parent.id) + for variable in obj._attrs['variables']: + schedule_variable = self._obj_cls(self, variable) + array.append(schedule_variable) + + return array + + +class ProjectPipelineSchedule(RESTObject): + _managers = ( + ('variables', 'ProjectPipelineScheduleVariableManager'), + ) + + +class ProjectPipelineSchedulesManager(RetrieveMixin, CreateMixin, RESTManager): + _path = '/projects/%(project_id)s/pipeline_schedules' + _obj_cls = ProjectPipelineSchedule + _from_parent_attrs = {'project_id': 'id'} + _create_attrs = (('description', 'ref', 'cron'), + ('cron_timezone', 'active')) + + def create(self, data, **kwargs): + """Creates a new object. + + Args: + data (dict): Parameters to send to the server to create the + resource + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabCreateError: If the server cannot perform the request + + Returns: + RESTObject: A new instance of the managed object class build with + the data sent by the server + """ + return CreateMixin.create(self, data, path=self.path, **kwargs) + + +class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): + pass + + class ProjectPipelineJob(ProjectJob): pass @@ -2323,6 +2386,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ('pagesdomains', 'ProjectPagesDomainManager'), ('pipelines', 'ProjectPipelineManager'), ('protectedbranches', 'ProjectProtectedBranchManager'), + ('pipeline_schedules', 'ProjectPipelineSchedulesManager'), ('runners', 'ProjectRunnerManager'), ('services', 'ProjectServiceManager'), ('snippets', 'ProjectSnippetManager'), From fd726cdb61a78aafb780cae56a7909e7b648e4dc Mon Sep 17 00:00:00 2001 From: Moritz Lipp Date: Fri, 13 Oct 2017 14:17:40 +0200 Subject: [PATCH 18/25] Project pipeline jobs --- gitlab/v4/objects.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index ea35bc707..19adb6296 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1952,6 +1952,18 @@ def raw(self, file_path, ref, streamed=False, action=None, chunk_size=1024, return utils.response_content(result, streamed, action, chunk_size) +class ProjectPipelineJob(ProjectJob): + pass + + +class ProjectPipelineJobsManager(ListMixin, RESTManager): + _path = '/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs' + _obj_cls = ProjectPipelineJob + _from_parent_attrs = {'project_id': 'project_id', + 'pipeline_id' : 'id'} + _list_filters = ('scope',) + + class ProjectPipeline(RESTObject): _managers = (('jobs', 'ProjectPipelineJobManager'), ) From 0a06779f563be22d5a654eaf1423494e31c6a35d Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 5 Feb 2018 15:49:17 +0100 Subject: [PATCH 19/25] Remove pipeline schedules from v3 (not supported) --- gitlab/v3/objects.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/gitlab/v3/objects.py b/gitlab/v3/objects.py index 9ea597c44..0db9dfd6b 100644 --- a/gitlab/v3/objects.py +++ b/gitlab/v3/objects.py @@ -1496,18 +1496,6 @@ class ProjectFileManager(BaseManager): obj_cls = ProjectFile -class ProjectPipelineSchedule(GitlabObject): - _url = '/projects/%(project_id)s/pipeline_schedules' - _create_url = '/projects/%(project_id)s/pipeline_schedules' - - requiredUrlAttrs = ['project_id'] - requiredCreateAttrs = ['description', 'ref', 'cron'] - - -class ProjectPipelineSchedulesManager(BaseManager): - obj_cls = ProjectPipelineSchedule - - class ProjectPipeline(GitlabObject): _url = '/projects/%(project_id)s/pipelines' _create_url = '/projects/%(project_id)s/pipeline' @@ -1815,7 +1803,6 @@ class Project(GitlabObject): ('notificationsettings', 'ProjectNotificationSettingsManager', [('project_id', 'id')]), ('pipelines', 'ProjectPipelineManager', [('project_id', 'id')]), - ('pipeline_schedules', 'ProjectPipelineSchedulesManager', [('project_id', 'id')]), ('runners', 'ProjectRunnerManager', [('project_id', 'id')]), ('services', 'ProjectServiceManager', [('project_id', 'id')]), ('snippets', 'ProjectSnippetManager', [('project_id', 'id')]), From e7546dee1fff0265116ae96668e049100f76b66c Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Mon, 5 Feb 2018 15:53:33 +0100 Subject: [PATCH 20/25] pep8 fixes --- gitlab/v4/objects.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index f6dcb086b..c1e644149 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -1960,7 +1960,7 @@ class ProjectPipelineJobsManager(ListMixin, RESTManager): _path = '/projects/%(project_id)s/pipelines/%(pipeline_id)s/jobs' _obj_cls = ProjectPipelineJob _from_parent_attrs = {'project_id': 'project_id', - 'pipeline_id' : 'id'} + 'pipeline_id': 'id'} _list_filters = ('scope',) @@ -2024,7 +2024,8 @@ def create(self, data, **kwargs): return CreateMixin.create(self, data, path=path, **kwargs) -class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): +class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, + RESTObject): _id_attr = 'key' @@ -2034,7 +2035,7 @@ class ProjectPipelineScheduleVariableManager(CreateMixin, UpdateMixin, '%(pipeline_schedule_id)s/variables') _obj_cls = ProjectPipelineScheduleVariable _from_parent_attrs = {'project_id': 'project_id', - 'pipeline_schedule_id' : 'id'} + 'pipeline_schedule_id': 'id'} _create_attrs = (('key', 'value'), tuple()) _update_attrs = (('key', 'value'), tuple()) @@ -2069,10 +2070,6 @@ class ProjectPipelineScheduleManager(CRUDMixin, RESTManager): ('description', 'ref', 'cron', 'cron_timezone', 'active')) -class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): - pass - - class ProjectPipelineJob(ProjectJob): pass From eb5c149af74f064aa1512fc1c6964e9ade5bb0c0 Mon Sep 17 00:00:00 2001 From: Miouge1 Date: Fri, 9 Feb 2018 08:20:35 +0100 Subject: [PATCH 21/25] Add documentation about labels update --- docs/gl_objects/mrs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/gl_objects/mrs.py b/docs/gl_objects/mrs.py index 1e54c80bb..7e11cc312 100644 --- a/docs/gl_objects/mrs.py +++ b/docs/gl_objects/mrs.py @@ -19,6 +19,7 @@ # update mr.description = 'New description' +mr.labels = ['foo', 'bar'] mr.save() # end update From d416238a73ea9f3b09fd04cbd46eeee2f231a499 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 18 Feb 2018 09:01:33 +0100 Subject: [PATCH 22/25] Move the pipelines doc to builds.rst --- docs/gl_objects/builds.rst | 52 ++++++++++++++++++++++++++++++++-- docs/gl_objects/projects.py | 20 ------------- docs/gl_objects/projects.rst | 54 ------------------------------------ 3 files changed, 49 insertions(+), 77 deletions(-) diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index 9d6873626..aebe16fb0 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -1,10 +1,56 @@ -############################### -Jobs (v4 API) / Builds (v3 API) -############################### +########################## +Pipelines, Builds and Jobs +########################## Build and job are two classes representing the same object. Builds are used in v3 API, jobs in v4 API. +Project pipelines +================= + +A pipeline is a group of jobs executed by GitLab CI. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectPipeline` + + :class:`gitlab.v4.objects.ProjectPipelineManager` + + :attr:`gitlab.v4.objects.Project.pipelines` + +* v3 API: + + + :class:`gitlab.v3.objects.ProjectPipeline` + + :class:`gitlab.v3.objects.ProjectPipelineManager` + + :attr:`gitlab.v3.objects.Project.pipelines` + + :attr:`gitlab.Gitlab.project_pipelines` + +* GitLab API: https://docs.gitlab.com/ce/api/pipelines.html + +Examples +-------- + +List pipelines for a project:: + + pipelines = project.pipelines.list() + +Get a pipeline for a project:: + + pipeline = project.pipelines.get(pipeline_id) + +Create a pipeline for a particular reference:: + + pipeline = project.pipelines.create({'ref': 'master'}) + +Retry the failed builds for a pipeline:: + + pipeline.retry() + +Cancel builds in a pipeline:: + + pipeline.cancel() + Triggers ======== diff --git a/docs/gl_objects/projects.py b/docs/gl_objects/projects.py index a633ee827..1b0a6b95d 100644 --- a/docs/gl_objects/projects.py +++ b/docs/gl_objects/projects.py @@ -321,26 +321,6 @@ service.delete() # end service delete -# pipeline list -pipelines = project.pipelines.list() -# end pipeline list - -# pipeline get -pipeline = project.pipelines.get(pipeline_id) -# end pipeline get - -# pipeline create -pipeline = project.pipelines.create({'ref': 'master'}) -# end pipeline create - -# pipeline retry -pipeline.retry() -# end pipeline retry - -# pipeline cancel -pipeline.cancel() -# end pipeline cancel - # boards list boards = project.boards.list() # end boards list diff --git a/docs/gl_objects/projects.rst b/docs/gl_objects/projects.rst index 0c556f451..b39c73b06 100644 --- a/docs/gl_objects/projects.rst +++ b/docs/gl_objects/projects.rst @@ -604,60 +604,6 @@ Delete a project hook: :start-after: # hook delete :end-before: # end hook delete -Project pipelines -================= - -Reference ---------- - -* v4 API: - - + :class:`gitlab.v4.objects.ProjectPipeline` - + :class:`gitlab.v4.objects.ProjectPipelineManager` - + :attr:`gitlab.v4.objects.Project.pipelines` - -* v3 API: - - + :class:`gitlab.v3.objects.ProjectPipeline` - + :class:`gitlab.v3.objects.ProjectPipelineManager` - + :attr:`gitlab.v3.objects.Project.pipelines` - + :attr:`gitlab.Gitlab.project_pipelines` - -* GitLab API: https://docs.gitlab.com/ce/api/pipelines.html - -Examples --------- - -List pipelines for a project: - -.. literalinclude:: projects.py - :start-after: # pipeline list - :end-before: # end pipeline list - -Get a pipeline for a project: - -.. literalinclude:: projects.py - :start-after: # pipeline get - :end-before: # end pipeline get - -Retry the failed builds for a pipeline: - -.. literalinclude:: projects.py - :start-after: # pipeline retry - :end-before: # end pipeline retry - -Cancel builds in a pipeline: - -.. literalinclude:: projects.py - :start-after: # pipeline cancel - :end-before: # end pipeline cancel - -Create a pipeline for a particular reference: - -.. literalinclude:: projects.py - :start-after: # pipeline create - :end-before: # end pipeline create - Project Services ================ From ac123dfe67240f25de52dc445bde93726d5862c1 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 18 Feb 2018 09:54:51 +0100 Subject: [PATCH 23/25] Add docs for pipeline schedules --- docs/gl_objects/builds.rst | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/gl_objects/builds.rst b/docs/gl_objects/builds.rst index aebe16fb0..2791188eb 100644 --- a/docs/gl_objects/builds.rst +++ b/docs/gl_objects/builds.rst @@ -102,6 +102,66 @@ Remove a trigger: :start-after: # trigger delete :end-before: # end trigger delete +Pipeline schedule +================= + +You can schedule pipeline runs using a cron-like syntax. Variables can be +associated with the scheduled pipelines. + +Reference +--------- + +* v4 API + + + :class:`gitlab.v4.objects.ProjectPipelineSchedule` + + :class:`gitlab.v4.objects.ProjectPipelineScheduleManager` + + :attr:`gitlab.v4.objects.Project.pipelineschedules` + + :class:`gitlab.v4.objects.ProjectPipelineScheduleVariable` + + :class:`gitlab.v4.objects.ProjectPipelineScheduleVariableManager` + + :attr:`gitlab.v4.objects.Project.pipelineschedules` + +* GitLab API: https://docs.gitlab.com/ce/api/pipeline_schedules.html + +Examples +-------- + +List pipeline schedules:: + + scheds = project.pipelineschedules.list() + +Get a single schedule:: + + sched = projects.pipelineschedules.get(schedule_id) + +Create a new schedule:: + + sched = project.pipelineschedules.create({ + 'ref': 'master', + 'description': 'Daily test', + 'cron': '0 1 * * *'}) + +Update a schedule:: + + sched.cron = '1 2 * * *' + sched.save() + +Delete a schedule:: + + sched.delete() + +Create a schedule variable:: + + var = sched.variables.create({'key': 'foo', 'value': 'bar'}) + +Edit a schedule variable:: + + var.value = 'new_value' + var.save() + +Delete a schedule variable:: + + var.delete() + Projects and groups variables ============================= From d63748a41cc22bba93a9adf0812e7eb7b74a0161 Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 18 Feb 2018 10:02:28 +0100 Subject: [PATCH 24/25] docs: trigger_pipeline only accept branches and tags as ref Fixes #430 --- gitlab/v4/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index c1e644149..f10754028 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -2681,7 +2681,7 @@ def trigger_pipeline(self, ref, token, variables={}, **kwargs): See https://gitlab.com/help/ci/triggers/README.md#trigger-a-build Args: - ref (str): Commit to build; can be a commit SHA, a branch name, ... + ref (str): Commit to build; can be a branch name or a tag token (str): The trigger token variables (dict): Variables passed to the build script **kwargs: Extra options to send to the server (e.g. sudo) From 10bd1f43f59b2257e6195b290b0dc8a578b7562a Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Sun, 18 Feb 2018 10:11:30 +0100 Subject: [PATCH 25/25] Prepare the 1.3.0 release --- AUTHORS | 6 +++++- ChangeLog.rst | 19 +++++++++++++++++++ gitlab/__init__.py | 4 ++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index ac5d28fac..c0bc7d6b5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,7 +36,6 @@ Erik Weatherwax fgouteroux Greg Allen Guillaume Delacour -Guyzmo Guyzmo hakkeroid Ian Sparks @@ -51,6 +50,7 @@ Jerome Robert Johan Brandhorst Jonathon Reinhart Jon Banafato +Keith Wansbrough Koen Smets Kris Gambirazzi Lyudmil Nenov @@ -59,11 +59,14 @@ massimone88 Matej Zerovnik Matt Odden Maura Hausman +Michael Overmeyer Michal Galet Mike Kobit Mikhail Lopotkov +Miouge1 Missionrulz Mond WAN +Moritz Lipp Nathan Giesbrecht Nathan Schmidt pa4373 @@ -74,6 +77,7 @@ Pete Browne Peter Mosmans P. F. Chimento Philipp Busch +Pierre Tardy Rafael Eyng Richard Hansen Robert Lu diff --git a/ChangeLog.rst b/ChangeLog.rst index 3049b9a0f..f1a45f27c 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,6 +1,24 @@ ChangeLog ========= +Version 1.3.0_ - 2018-02-18 +--------------------------- + +* Add support for pipeline schedules and schedule variables +* Clarify information about supported python version +* Add manager for jobs within a pipeline +* Fix wrong tag example +* Update the groups documentation +* Add support for MR participants API +* Add support for getting list of user projects +* Add Gitlab and User events support +* Make trigger_pipeline return the pipeline +* Config: support api_version in the global section +* Gitlab can be used as context manager +* Default to API v4 +* Add a simplified example for streamed artifacts +* Add documentation about labels update + Version 1.2.0_ - 2018-01-01 --------------------------- @@ -535,6 +553,7 @@ Version 0.1 - 2013-07-08 * Initial release +.. _1.3.0: https://github.com/python-gitlab/python-gitlab/compare/1.2.0...1.3.0 .. _1.2.0: https://github.com/python-gitlab/python-gitlab/compare/1.1.0...1.2.0 .. _1.1.0: https://github.com/python-gitlab/python-gitlab/compare/1.0.2...1.1.0 .. _1.0.2: https://github.com/python-gitlab/python-gitlab/compare/1.0.1...1.0.2 diff --git a/gitlab/__init__.py b/gitlab/__init__.py index c909f9f06..17e60bccf 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -34,11 +34,11 @@ from gitlab.v3.objects import * # noqa __title__ = 'python-gitlab' -__version__ = '1.2.0' +__version__ = '1.3.0' __author__ = 'Gauvain Pocentek' __email__ = 'gauvain@pocentek.net' __license__ = 'LGPL3' -__copyright__ = 'Copyright 2013-2017 Gauvain Pocentek' +__copyright__ = 'Copyright 2013-2018 Gauvain Pocentek' warnings.filterwarnings('default', category=DeprecationWarning, module='^gitlab')