python-gitlab 4.4.0__py3-none-any.whl → 4.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. gitlab/_backends/protocol.py +2 -4
  2. gitlab/_version.py +1 -1
  3. gitlab/cli.py +33 -5
  4. gitlab/client.py +2 -3
  5. gitlab/mixins.py +22 -14
  6. gitlab/utils.py +3 -3
  7. gitlab/v4/cli.py +32 -11
  8. gitlab/v4/objects/__init__.py +1 -0
  9. gitlab/v4/objects/artifacts.py +7 -3
  10. gitlab/v4/objects/audit_events.py +1 -0
  11. gitlab/v4/objects/branches.py +10 -3
  12. gitlab/v4/objects/ci_lint.py +4 -4
  13. gitlab/v4/objects/commits.py +6 -6
  14. gitlab/v4/objects/container_registry.py +2 -2
  15. gitlab/v4/objects/deploy_keys.py +3 -1
  16. gitlab/v4/objects/deployments.py +3 -2
  17. gitlab/v4/objects/environments.py +1 -1
  18. gitlab/v4/objects/features.py +1 -0
  19. gitlab/v4/objects/files.py +15 -7
  20. gitlab/v4/objects/geo_nodes.py +4 -4
  21. gitlab/v4/objects/groups.py +13 -7
  22. gitlab/v4/objects/integrations.py +3 -1
  23. gitlab/v4/objects/issues.py +6 -4
  24. gitlab/v4/objects/iterations.py +29 -2
  25. gitlab/v4/objects/job_token_scope.py +48 -0
  26. gitlab/v4/objects/jobs.py +15 -15
  27. gitlab/v4/objects/merge_request_approvals.py +12 -59
  28. gitlab/v4/objects/merge_requests.py +14 -12
  29. gitlab/v4/objects/milestones.py +4 -4
  30. gitlab/v4/objects/namespaces.py +3 -1
  31. gitlab/v4/objects/packages.py +4 -4
  32. gitlab/v4/objects/pipelines.py +6 -5
  33. gitlab/v4/objects/projects.py +22 -18
  34. gitlab/v4/objects/repositories.py +14 -9
  35. gitlab/v4/objects/runners.py +2 -2
  36. gitlab/v4/objects/secure_files.py +2 -1
  37. gitlab/v4/objects/service_accounts.py +18 -0
  38. gitlab/v4/objects/sidekiq.py +4 -4
  39. gitlab/v4/objects/snippets.py +3 -3
  40. gitlab/v4/objects/todos.py +2 -2
  41. gitlab/v4/objects/topics.py +2 -2
  42. gitlab/v4/objects/users.py +11 -10
  43. gitlab/v4/objects/variables.py +1 -0
  44. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/METADATA +20 -3
  45. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/RECORD +50 -49
  46. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/WHEEL +1 -1
  47. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/AUTHORS +0 -0
  48. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/COPYING +0 -0
  49. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/entry_points.txt +0 -0
  50. {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/top_level.txt +0 -0
@@ -40,6 +40,7 @@ class ProjectFile(SaveMixin, ObjectDeleteMixin, RESTObject):
40
40
  commit_message: str
41
41
  file_path: str
42
42
  manager: "ProjectFileManager"
43
+ content: str # since the `decode()` method uses `self.content`
43
44
 
44
45
  def decode(self) -> bytes:
45
46
  """Returns the decoded content of the file.
@@ -108,7 +109,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
108
109
  optional=("encoding", "author_email", "author_name"),
109
110
  )
110
111
 
111
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
112
+ @cli.register_custom_action(
113
+ cls_names="ProjectFileManager", required=("file_path", "ref")
114
+ )
112
115
  # NOTE(jlvillal): Signature doesn't match UpdateMixin.update() so ignore
113
116
  # type error
114
117
  def get( # type: ignore
@@ -131,9 +134,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
131
134
  return cast(ProjectFile, GetMixin.get(self, file_path, ref=ref, **kwargs))
132
135
 
133
136
  @cli.register_custom_action(
134
- "ProjectFileManager",
135
- ("file_path", "branch", "content", "commit_message"),
136
- ("encoding", "author_email", "author_name"),
137
+ cls_names="ProjectFileManager",
138
+ required=("file_path", "branch", "content", "commit_message"),
139
+ optional=("encoding", "author_email", "author_name"),
137
140
  )
138
141
  @exc.on_http_error(exc.GitlabCreateError)
139
142
  def create(
@@ -198,7 +201,8 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
198
201
  return result
199
202
 
200
203
  @cli.register_custom_action(
201
- "ProjectFileManager", ("file_path", "branch", "commit_message")
204
+ cls_names="ProjectFileManager",
205
+ required=("file_path", "branch", "commit_message"),
202
206
  )
203
207
  @exc.on_http_error(exc.GitlabDeleteError)
204
208
  # NOTE(jlvillal): Signature doesn't match DeleteMixin.delete() so ignore
@@ -223,7 +227,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
223
227
  data = {"branch": branch, "commit_message": commit_message}
224
228
  self.gitlab.http_delete(path, query_data=data, **kwargs)
225
229
 
226
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
230
+ @cli.register_custom_action(
231
+ cls_names="ProjectFileManager", required=("file_path", "ref")
232
+ )
227
233
  @exc.on_http_error(exc.GitlabGetError)
228
234
  def raw(
229
235
  self,
@@ -270,7 +276,9 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
270
276
  result, streamed, action, chunk_size, iterator=iterator
271
277
  )
272
278
 
273
- @cli.register_custom_action("ProjectFileManager", ("file_path", "ref"))
279
+ @cli.register_custom_action(
280
+ cls_names="ProjectFileManager", required=("file_path", "ref")
281
+ )
274
282
  @exc.on_http_error(exc.GitlabListError)
275
283
  def blame(self, file_path: str, ref: str, **kwargs: Any) -> List[Dict[str, Any]]:
276
284
  """Return the content of a file for a commit.
@@ -19,7 +19,7 @@ __all__ = [
19
19
 
20
20
 
21
21
  class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject):
22
- @cli.register_custom_action("GeoNode")
22
+ @cli.register_custom_action(cls_names="GeoNode")
23
23
  @exc.on_http_error(exc.GitlabRepairError)
24
24
  def repair(self, **kwargs: Any) -> None:
25
25
  """Repair the OAuth authentication of the geo node.
@@ -37,7 +37,7 @@ class GeoNode(SaveMixin, ObjectDeleteMixin, RESTObject):
37
37
  assert isinstance(server_data, dict)
38
38
  self._update_attrs(server_data)
39
39
 
40
- @cli.register_custom_action("GeoNode")
40
+ @cli.register_custom_action(cls_names="GeoNode")
41
41
  @exc.on_http_error(exc.GitlabGetError)
42
42
  def status(self, **kwargs: Any) -> Dict[str, Any]:
43
43
  """Get the status of the geo node.
@@ -69,7 +69,7 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager):
69
69
  def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> GeoNode:
70
70
  return cast(GeoNode, super().get(id=id, lazy=lazy, **kwargs))
71
71
 
72
- @cli.register_custom_action("GeoNodeManager")
72
+ @cli.register_custom_action(cls_names="GeoNodeManager")
73
73
  @exc.on_http_error(exc.GitlabGetError)
74
74
  def status(self, **kwargs: Any) -> List[Dict[str, Any]]:
75
75
  """Get the status of all the geo nodes.
@@ -89,7 +89,7 @@ class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager):
89
89
  assert isinstance(result, list)
90
90
  return result
91
91
 
92
- @cli.register_custom_action("GeoNodeManager")
92
+ @cli.register_custom_action(cls_names="GeoNodeManager")
93
93
  @exc.on_http_error(exc.GitlabGetError)
94
94
  def current_failures(self, **kwargs: Any) -> List[Dict[str, Any]]:
95
95
  """Get the list of failures on the current geo node.
@@ -46,6 +46,7 @@ from .packages import GroupPackageManager # noqa: F401
46
46
  from .projects import GroupProjectManager, SharedProjectManager # noqa: F401
47
47
  from .push_rules import GroupPushRulesManager
48
48
  from .runners import GroupRunnerManager # noqa: F401
49
+ from .service_accounts import GroupServiceAccountManager # noqa: F401
49
50
  from .statistics import GroupIssuesStatisticsManager # noqa: F401
50
51
  from .variables import GroupVariableManager # noqa: F401
51
52
  from .wikis import GroupWikiManager # noqa: F401
@@ -102,8 +103,9 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
102
103
  variables: GroupVariableManager
103
104
  wikis: GroupWikiManager
104
105
  saml_group_links: "GroupSAMLGroupLinkManager"
106
+ service_accounts: "GroupServiceAccountManager"
105
107
 
106
- @cli.register_custom_action("Group", ("project_id",))
108
+ @cli.register_custom_action(cls_names="Group", required=("project_id",))
107
109
  @exc.on_http_error(exc.GitlabTransferProjectError)
108
110
  def transfer_project(self, project_id: int, **kwargs: Any) -> None:
109
111
  """Transfer a project to this group.
@@ -119,7 +121,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
119
121
  path = f"/groups/{self.encoded_id}/projects/{project_id}"
120
122
  self.manager.gitlab.http_post(path, **kwargs)
121
123
 
122
- @cli.register_custom_action("Group", (), ("group_id",))
124
+ @cli.register_custom_action(cls_names="Group", required=(), optional=("group_id",))
123
125
  @exc.on_http_error(exc.GitlabGroupTransferError)
124
126
  def transfer(self, group_id: Optional[int] = None, **kwargs: Any) -> None:
125
127
  """Transfer the group to a new parent group or make it a top-level group.
@@ -141,7 +143,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
141
143
  post_data["group_id"] = group_id
142
144
  self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
143
145
 
144
- @cli.register_custom_action("Group", ("scope", "search"))
146
+ @cli.register_custom_action(cls_names="Group", required=("scope", "search"))
145
147
  @exc.on_http_error(exc.GitlabSearchError)
146
148
  def search(
147
149
  self, scope: str, search: str, **kwargs: Any
@@ -164,7 +166,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
164
166
  path = f"/groups/{self.encoded_id}/search"
165
167
  return self.manager.gitlab.http_list(path, query_data=data, **kwargs)
166
168
 
167
- @cli.register_custom_action("Group")
169
+ @cli.register_custom_action(cls_names="Group")
168
170
  @exc.on_http_error(exc.GitlabCreateError)
169
171
  def ldap_sync(self, **kwargs: Any) -> None:
170
172
  """Sync LDAP groups.
@@ -179,7 +181,11 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
179
181
  path = f"/groups/{self.encoded_id}/ldap_sync"
180
182
  self.manager.gitlab.http_post(path, **kwargs)
181
183
 
182
- @cli.register_custom_action("Group", ("group_id", "group_access"), ("expires_at",))
184
+ @cli.register_custom_action(
185
+ cls_names="Group",
186
+ required=("group_id", "group_access"),
187
+ optional=("expires_at",),
188
+ )
183
189
  @exc.on_http_error(exc.GitlabCreateError)
184
190
  def share(
185
191
  self,
@@ -213,7 +219,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
213
219
  assert isinstance(server_data, dict)
214
220
  self._update_attrs(server_data)
215
221
 
216
- @cli.register_custom_action("Group", ("group_id",))
222
+ @cli.register_custom_action(cls_names="Group", required=("group_id",))
217
223
  @exc.on_http_error(exc.GitlabDeleteError)
218
224
  def unshare(self, group_id: int, **kwargs: Any) -> None:
219
225
  """Delete a shared group link within a group.
@@ -229,7 +235,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
229
235
  path = f"/groups/{self.encoded_id}/share/{group_id}"
230
236
  self.manager.gitlab.http_delete(path, **kwargs)
231
237
 
232
- @cli.register_custom_action("Group")
238
+ @cli.register_custom_action(cls_names="Group")
233
239
  @exc.on_http_error(exc.GitlabRestoreError)
234
240
  def restore(self, **kwargs: Any) -> None:
235
241
  """Restore a group marked for deletion..
@@ -270,7 +270,9 @@ class ProjectIntegrationManager(
270
270
  ) -> ProjectIntegration:
271
271
  return cast(ProjectIntegration, super().get(id=id, lazy=lazy, **kwargs))
272
272
 
273
- @cli.register_custom_action(("ProjectIntegrationManager", "ProjectServiceManager"))
273
+ @cli.register_custom_action(
274
+ cls_names=("ProjectIntegrationManager", "ProjectServiceManager")
275
+ )
274
276
  def available(self) -> List[str]:
275
277
  """List the services known by python-gitlab.
276
278
 
@@ -126,7 +126,7 @@ class ProjectIssue(
126
126
  resource_iteration_events: ProjectIssueResourceIterationEventManager
127
127
  resource_weight_events: ProjectIssueResourceWeightEventManager
128
128
 
129
- @cli.register_custom_action("ProjectIssue", ("to_project_id",))
129
+ @cli.register_custom_action(cls_names="ProjectIssue", required=("to_project_id",))
130
130
  @exc.on_http_error(exc.GitlabUpdateError)
131
131
  def move(self, to_project_id: int, **kwargs: Any) -> None:
132
132
  """Move the issue to another project.
@@ -146,7 +146,9 @@ class ProjectIssue(
146
146
  assert isinstance(server_data, dict)
147
147
  self._update_attrs(server_data)
148
148
 
149
- @cli.register_custom_action("ProjectIssue", ("move_after_id", "move_before_id"))
149
+ @cli.register_custom_action(
150
+ cls_names="ProjectIssue", required=("move_after_id", "move_before_id")
151
+ )
150
152
  @exc.on_http_error(exc.GitlabUpdateError)
151
153
  def reorder(
152
154
  self,
@@ -178,7 +180,7 @@ class ProjectIssue(
178
180
  assert isinstance(server_data, dict)
179
181
  self._update_attrs(server_data)
180
182
 
181
- @cli.register_custom_action("ProjectIssue")
183
+ @cli.register_custom_action(cls_names="ProjectIssue")
182
184
  @exc.on_http_error(exc.GitlabGetError)
183
185
  def related_merge_requests(self, **kwargs: Any) -> Dict[str, Any]:
184
186
  """List merge requests related to the issue.
@@ -199,7 +201,7 @@ class ProjectIssue(
199
201
  assert isinstance(result, dict)
200
202
  return result
201
203
 
202
- @cli.register_custom_action("ProjectIssue")
204
+ @cli.register_custom_action(cls_names="ProjectIssue")
203
205
  @exc.on_http_error(exc.GitlabGetError)
204
206
  def closed_by(self, **kwargs: Any) -> Dict[str, Any]:
205
207
  """List merge requests that will close the issue when merged.
@@ -1,3 +1,4 @@
1
+ from gitlab import types
1
2
  from gitlab.base import RESTManager, RESTObject
2
3
  from gitlab.mixins import ListMixin
3
4
 
@@ -16,11 +17,37 @@ class GroupIterationManager(ListMixin, RESTManager):
16
17
  _path = "/groups/{group_id}/iterations"
17
18
  _obj_cls = GroupIteration
18
19
  _from_parent_attrs = {"group_id": "id"}
19
- _list_filters = ("state", "search", "include_ancestors")
20
+ # When using the API, the "in" keyword collides with python's "in" keyword
21
+ # raising a SyntaxError.
22
+ # For this reason, we have to use the query_parameters argument:
23
+ # group.iterations.list(query_parameters={"in": "title"})
24
+ _list_filters = (
25
+ "include_ancestors",
26
+ "include_descendants",
27
+ "in",
28
+ "search",
29
+ "state",
30
+ "updated_after",
31
+ "updated_before",
32
+ )
33
+ _types = {"in": types.ArrayAttribute}
20
34
 
21
35
 
22
36
  class ProjectIterationManager(ListMixin, RESTManager):
23
37
  _path = "/projects/{project_id}/iterations"
24
38
  _obj_cls = GroupIteration
25
39
  _from_parent_attrs = {"project_id": "id"}
26
- _list_filters = ("state", "search", "include_ancestors")
40
+ # When using the API, the "in" keyword collides with python's "in" keyword
41
+ # raising a SyntaxError.
42
+ # For this reason, we have to use the query_parameters argument:
43
+ # project.iterations.list(query_parameters={"in": "title"})
44
+ _list_filters = (
45
+ "include_ancestors",
46
+ "include_descendants",
47
+ "in",
48
+ "search",
49
+ "state",
50
+ "updated_after",
51
+ "updated_before",
52
+ )
53
+ _types = {"in": types.ArrayAttribute}
@@ -2,12 +2,17 @@ from typing import Any, cast
2
2
 
3
3
  from gitlab.base import RESTManager, RESTObject
4
4
  from gitlab.mixins import (
5
+ CreateMixin,
6
+ DeleteMixin,
5
7
  GetWithoutIdMixin,
8
+ ListMixin,
9
+ ObjectDeleteMixin,
6
10
  RefreshMixin,
7
11
  SaveMixin,
8
12
  UpdateMethod,
9
13
  UpdateMixin,
10
14
  )
15
+ from gitlab.types import RequiredOptional
11
16
 
12
17
  __all__ = [
13
18
  "ProjectJobTokenScope",
@@ -18,6 +23,9 @@ __all__ = [
18
23
  class ProjectJobTokenScope(RefreshMixin, SaveMixin, RESTObject):
19
24
  _id_attr = None
20
25
 
26
+ allowlist: "AllowlistProjectManager"
27
+ groups_allowlist: "AllowlistGroupManager"
28
+
21
29
 
22
30
  class ProjectJobTokenScopeManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
23
31
  _path = "/projects/{project_id}/job_token_scope"
@@ -27,3 +35,43 @@ class ProjectJobTokenScopeManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
27
35
 
28
36
  def get(self, **kwargs: Any) -> ProjectJobTokenScope:
29
37
  return cast(ProjectJobTokenScope, super().get(**kwargs))
38
+
39
+
40
+ class AllowlistProject(ObjectDeleteMixin, RESTObject):
41
+ _id_attr = "target_project_id" # note: only true for create endpoint
42
+
43
+ def get_id(self) -> int:
44
+ """Returns the id of the resource. This override deals with
45
+ the fact that either an `id` or a `target_project_id` attribute
46
+ is returned by the server depending on the endpoint called."""
47
+ target_project_id = cast(int, super().get_id())
48
+ if target_project_id is not None:
49
+ return target_project_id
50
+ return cast(int, self.id)
51
+
52
+
53
+ class AllowlistProjectManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
54
+ _path = "/projects/{project_id}/job_token_scope/allowlist"
55
+ _obj_cls = AllowlistProject
56
+ _from_parent_attrs = {"project_id": "project_id"}
57
+ _create_attrs = RequiredOptional(required=("target_project_id",))
58
+
59
+
60
+ class AllowlistGroup(ObjectDeleteMixin, RESTObject):
61
+ _id_attr = "target_group_id" # note: only true for create endpoint
62
+
63
+ def get_id(self) -> int:
64
+ """Returns the id of the resource. This override deals with
65
+ the fact that either an `id` or a `target_group_id` attribute
66
+ is returned by the server depending on the endpoint called."""
67
+ target_group_id = cast(int, super().get_id())
68
+ if target_group_id is not None:
69
+ return target_group_id
70
+ return cast(int, self.id)
71
+
72
+
73
+ class AllowlistGroupManager(ListMixin, CreateMixin, DeleteMixin, RESTManager):
74
+ _path = "/projects/{project_id}/job_token_scope/groups_allowlist"
75
+ _obj_cls = AllowlistGroup
76
+ _from_parent_attrs = {"project_id": "project_id"}
77
+ _create_attrs = RequiredOptional(required=("target_group_id",))
gitlab/v4/objects/jobs.py CHANGED
@@ -16,7 +16,7 @@ __all__ = [
16
16
 
17
17
 
18
18
  class ProjectJob(RefreshMixin, RESTObject):
19
- @cli.register_custom_action("ProjectJob")
19
+ @cli.register_custom_action(cls_names="ProjectJob")
20
20
  @exc.on_http_error(exc.GitlabJobCancelError)
21
21
  def cancel(self, **kwargs: Any) -> Dict[str, Any]:
22
22
  """Cancel the job.
@@ -34,7 +34,7 @@ class ProjectJob(RefreshMixin, RESTObject):
34
34
  assert isinstance(result, dict)
35
35
  return result
36
36
 
37
- @cli.register_custom_action("ProjectJob")
37
+ @cli.register_custom_action(cls_names="ProjectJob")
38
38
  @exc.on_http_error(exc.GitlabJobRetryError)
39
39
  def retry(self, **kwargs: Any) -> Dict[str, Any]:
40
40
  """Retry the job.
@@ -52,7 +52,7 @@ class ProjectJob(RefreshMixin, RESTObject):
52
52
  assert isinstance(result, dict)
53
53
  return result
54
54
 
55
- @cli.register_custom_action("ProjectJob")
55
+ @cli.register_custom_action(cls_names="ProjectJob")
56
56
  @exc.on_http_error(exc.GitlabJobPlayError)
57
57
  def play(self, **kwargs: Any) -> None:
58
58
  """Trigger a job explicitly.
@@ -65,9 +65,12 @@ class ProjectJob(RefreshMixin, RESTObject):
65
65
  GitlabJobPlayError: If the job could not be triggered
66
66
  """
67
67
  path = f"{self.manager.path}/{self.encoded_id}/play"
68
- self.manager.gitlab.http_post(path, **kwargs)
68
+ result = self.manager.gitlab.http_post(path, **kwargs)
69
+ if TYPE_CHECKING:
70
+ assert isinstance(result, dict)
71
+ self._update_attrs(result)
69
72
 
70
- @cli.register_custom_action("ProjectJob")
73
+ @cli.register_custom_action(cls_names="ProjectJob")
71
74
  @exc.on_http_error(exc.GitlabJobEraseError)
72
75
  def erase(self, **kwargs: Any) -> None:
73
76
  """Erase the job (remove job artifacts and trace).
@@ -82,7 +85,7 @@ class ProjectJob(RefreshMixin, RESTObject):
82
85
  path = f"{self.manager.path}/{self.encoded_id}/erase"
83
86
  self.manager.gitlab.http_post(path, **kwargs)
84
87
 
85
- @cli.register_custom_action("ProjectJob")
88
+ @cli.register_custom_action(cls_names="ProjectJob")
86
89
  @exc.on_http_error(exc.GitlabCreateError)
87
90
  def keep_artifacts(self, **kwargs: Any) -> None:
88
91
  """Prevent artifacts from being deleted when expiration is set.
@@ -97,7 +100,7 @@ class ProjectJob(RefreshMixin, RESTObject):
97
100
  path = f"{self.manager.path}/{self.encoded_id}/artifacts/keep"
98
101
  self.manager.gitlab.http_post(path, **kwargs)
99
102
 
100
- @cli.register_custom_action("ProjectJob")
103
+ @cli.register_custom_action(cls_names="ProjectJob")
101
104
  @exc.on_http_error(exc.GitlabCreateError)
102
105
  def delete_artifacts(self, **kwargs: Any) -> None:
103
106
  """Delete artifacts of a job.
@@ -112,7 +115,7 @@ class ProjectJob(RefreshMixin, RESTObject):
112
115
  path = f"{self.manager.path}/{self.encoded_id}/artifacts"
113
116
  self.manager.gitlab.http_delete(path, **kwargs)
114
117
 
115
- @cli.register_custom_action("ProjectJob")
118
+ @cli.register_custom_action(cls_names="ProjectJob")
116
119
  @exc.on_http_error(exc.GitlabGetError)
117
120
  def artifacts(
118
121
  self,
@@ -153,7 +156,7 @@ class ProjectJob(RefreshMixin, RESTObject):
153
156
  result, streamed, action, chunk_size, iterator=iterator
154
157
  )
155
158
 
156
- @cli.register_custom_action("ProjectJob")
159
+ @cli.register_custom_action(cls_names="ProjectJob")
157
160
  @exc.on_http_error(exc.GitlabGetError)
158
161
  def artifact(
159
162
  self,
@@ -196,7 +199,7 @@ class ProjectJob(RefreshMixin, RESTObject):
196
199
  result, streamed, action, chunk_size, iterator=iterator
197
200
  )
198
201
 
199
- @cli.register_custom_action("ProjectJob")
202
+ @cli.register_custom_action(cls_names="ProjectJob")
200
203
  @exc.on_http_error(exc.GitlabGetError)
201
204
  def trace(
202
205
  self,
@@ -206,7 +209,7 @@ class ProjectJob(RefreshMixin, RESTObject):
206
209
  *,
207
210
  iterator: bool = False,
208
211
  **kwargs: Any,
209
- ) -> Dict[str, Any]:
212
+ ) -> Optional[Union[bytes, Iterator[Any]]]:
210
213
  """Get the job trace.
211
214
 
212
215
  Args:
@@ -233,12 +236,9 @@ class ProjectJob(RefreshMixin, RESTObject):
233
236
  )
234
237
  if TYPE_CHECKING:
235
238
  assert isinstance(result, requests.Response)
236
- return_value = utils.response_content(
239
+ return utils.response_content(
237
240
  result, streamed, action, chunk_size, iterator=iterator
238
241
  )
239
- if TYPE_CHECKING:
240
- assert isinstance(return_value, dict)
241
- return return_value
242
242
 
243
243
 
244
244
  class ProjectJobManager(RetrieveMixin, RESTManager):
@@ -1,4 +1,4 @@
1
- from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
1
+ from typing import Any, cast, List, Optional, TYPE_CHECKING, Union
2
2
 
3
3
  from gitlab import exceptions as exc
4
4
  from gitlab.base import RESTManager, RESTObject
@@ -89,6 +89,8 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTMan
89
89
  approver_ids: Optional[List[int]] = None,
90
90
  approver_group_ids: Optional[List[int]] = None,
91
91
  approval_rule_name: str = "name",
92
+ *,
93
+ approver_usernames: Optional[List[str]] = None,
92
94
  **kwargs: Any,
93
95
  ) -> RESTObject:
94
96
  """Change MR-level allowed approvers and approver groups.
@@ -104,6 +106,7 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTMan
104
106
  """
105
107
  approver_ids = approver_ids or []
106
108
  approver_group_ids = approver_group_ids or []
109
+ approver_usernames = approver_usernames or []
107
110
 
108
111
  data = {
109
112
  "name": approval_rule_name,
@@ -111,6 +114,7 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTMan
111
114
  "rule_type": "regular",
112
115
  "user_ids": approver_ids,
113
116
  "group_ids": approver_group_ids,
117
+ "usernames": approver_usernames,
114
118
  }
115
119
  if TYPE_CHECKING:
116
120
  assert self._parent is not None
@@ -118,12 +122,13 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTMan
118
122
  self._parent.approval_rules
119
123
  )
120
124
  # update any existing approval rule matching the name
121
- existing_approval_rules = approval_rules.list()
125
+ existing_approval_rules = approval_rules.list(iterator=True)
122
126
  for ar in existing_approval_rules:
123
127
  if ar.name == approval_rule_name:
124
128
  ar.user_ids = data["user_ids"]
125
129
  ar.approvals_required = data["approvals_required"]
126
130
  ar.group_ids = data["group_ids"]
131
+ ar.usernames = data["usernames"]
127
132
  ar.save()
128
133
  return ar
129
134
  # if there was no rule matching the rule name, create a new one
@@ -132,53 +137,27 @@ class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTMan
132
137
 
133
138
  class ProjectMergeRequestApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject):
134
139
  _repr_attr = "name"
135
- id: int
136
- approval_rule_id: int
137
- merge_request_iid: int
138
-
139
- @exc.on_http_error(exc.GitlabUpdateError)
140
- def save(self, **kwargs: Any) -> None:
141
- """Save the changes made to the object to the server.
142
-
143
- The object is updated to match what the server returns.
144
-
145
- Args:
146
- **kwargs: Extra options to send to the server (e.g. sudo)
147
-
148
- Raise:
149
- GitlabAuthenticationError: If authentication is not correct
150
- GitlabUpdateError: If the server cannot perform the request
151
- """
152
- # There is a mismatch between the name of our id attribute and the put
153
- # REST API name for the project_id, so we override it here.
154
- self.approval_rule_id = self.id
155
- self.merge_request_iid = self._parent_attrs["mr_iid"]
156
- self.id = self._parent_attrs["project_id"]
157
- # save will update self.id with the result from the server, so no need
158
- # to overwrite with what it was before we overwrote it.
159
- SaveMixin.save(self, **kwargs)
160
140
 
161
141
 
162
142
  class ProjectMergeRequestApprovalRuleManager(CRUDMixin, RESTManager):
163
- _path = "/projects/{project_id}/merge_requests/{mr_iid}/approval_rules"
143
+ _path = "/projects/{project_id}/merge_requests/{merge_request_iid}/approval_rules"
164
144
  _obj_cls = ProjectMergeRequestApprovalRule
165
- _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"}
145
+ _from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"}
166
146
  _update_attrs = RequiredOptional(
167
147
  required=(
168
148
  "id",
169
149
  "merge_request_iid",
170
- "approval_rule_id",
171
150
  "name",
172
151
  "approvals_required",
173
152
  ),
174
- optional=("user_ids", "group_ids"),
153
+ optional=("user_ids", "group_ids", "usernames"),
175
154
  )
176
155
  # Important: When approval_project_rule_id is set, the name, users and
177
156
  # groups of project-level rule will be copied. The approvals_required
178
157
  # specified will be used.
179
158
  _create_attrs = RequiredOptional(
180
- required=("id", "merge_request_iid", "name", "approvals_required"),
181
- optional=("approval_project_rule_id", "user_ids", "group_ids"),
159
+ required=("name", "approvals_required"),
160
+ optional=("approval_project_rule_id", "user_ids", "group_ids", "usernames"),
182
161
  )
183
162
 
184
163
  def get(
@@ -188,32 +167,6 @@ class ProjectMergeRequestApprovalRuleManager(CRUDMixin, RESTManager):
188
167
  ProjectMergeRequestApprovalRule, super().get(id=id, lazy=lazy, **kwargs)
189
168
  )
190
169
 
191
- def create(
192
- self, data: Optional[Dict[str, Any]] = None, **kwargs: Any
193
- ) -> RESTObject:
194
- """Create a new object.
195
-
196
- Args:
197
- data: Parameters to send to the server to create the
198
- resource
199
- **kwargs: Extra options to send to the server (e.g. sudo or
200
- 'ref_name', 'stage', 'name', 'all')
201
-
202
- Raises:
203
- GitlabAuthenticationError: If authentication is not correct
204
- GitlabCreateError: If the server cannot perform the request
205
-
206
- Returns:
207
- A new instance of the manage object class build with
208
- the data sent by the server
209
- """
210
- if TYPE_CHECKING:
211
- assert data is not None
212
- new_data = data.copy()
213
- new_data["id"] = self._from_parent_attrs["project_id"]
214
- new_data["merge_request_iid"] = self._from_parent_attrs["mr_iid"]
215
- return CreateMixin.create(self, new_data, **kwargs)
216
-
217
170
 
218
171
  class ProjectMergeRequestApprovalState(RESTObject):
219
172
  pass