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
@@ -13,8 +13,7 @@ else:
13
13
 
14
14
  class BackendResponse(Protocol):
15
15
  @abc.abstractmethod
16
- def __init__(self, response: requests.Response) -> None:
17
- ...
16
+ def __init__(self, response: requests.Response) -> None: ...
18
17
 
19
18
 
20
19
  class Backend(Protocol):
@@ -30,5 +29,4 @@ class Backend(Protocol):
30
29
  verify: Optional[Union[bool, str]],
31
30
  stream: Optional[bool],
32
31
  **kwargs: Any,
33
- ) -> BackendResponse:
34
- ...
32
+ ) -> BackendResponse: ...
gitlab/_version.py CHANGED
@@ -3,4 +3,4 @@ __copyright__ = "Copyright 2013-2019 Gauvain Pocentek, 2019-2023 python-gitlab t
3
3
  __email__ = "gauvainpocentek@gmail.com"
4
4
  __license__ = "LGPL3"
5
5
  __title__ = "python-gitlab"
6
- __version__ = "4.4.0"
6
+ __version__ = "4.6.0"
gitlab/cli.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import dataclasses
2
3
  import functools
3
4
  import os
4
5
  import pathlib
@@ -29,12 +30,21 @@ from gitlab.base import RESTObject
29
30
  camel_upperlower_regex = re.compile(r"([A-Z]+)([A-Z][a-z])")
30
31
  camel_lowerupper_regex = re.compile(r"([a-z\d])([A-Z])")
31
32
 
33
+
34
+ @dataclasses.dataclass
35
+ class CustomAction:
36
+ required: Tuple[str, ...]
37
+ optional: Tuple[str, ...]
38
+ in_object: bool
39
+ requires_id: bool # if the `_id_attr` value should be a required argument
40
+
41
+
32
42
  # custom_actions = {
33
43
  # cls: {
34
44
  # action: (mandatory_args, optional_args, in_obj),
35
45
  # },
36
46
  # }
37
- custom_actions: Dict[str, Dict[str, Tuple[Tuple[str, ...], Tuple[str, ...], bool]]] = {}
47
+ custom_actions: Dict[str, Dict[str, CustomAction]] = {}
38
48
 
39
49
 
40
50
  # For an explanation of how these type-hints work see:
@@ -72,10 +82,12 @@ class VerticalHelpFormatter(argparse.HelpFormatter):
72
82
 
73
83
 
74
84
  def register_custom_action(
85
+ *,
75
86
  cls_names: Union[str, Tuple[str, ...]],
76
- mandatory: Tuple[str, ...] = (),
87
+ required: Tuple[str, ...] = (),
77
88
  optional: Tuple[str, ...] = (),
78
89
  custom_action: Optional[str] = None,
90
+ requires_id: bool = True, # if the `_id_attr` value should be a required argument
79
91
  ) -> Callable[[__F], __F]:
80
92
  def wrap(f: __F) -> __F:
81
93
  @functools.wraps(f)
@@ -98,7 +110,12 @@ def register_custom_action(
98
110
  custom_actions[final_name] = {}
99
111
 
100
112
  action = custom_action or f.__name__.replace("_", "-")
101
- custom_actions[final_name][action] = (mandatory, optional, in_obj)
113
+ custom_actions[final_name][action] = CustomAction(
114
+ required=required,
115
+ optional=optional,
116
+ in_object=in_obj,
117
+ requires_id=requires_id,
118
+ )
102
119
 
103
120
  return cast(__F, wrapped_f)
104
121
 
@@ -134,7 +151,6 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser:
134
151
  parser = argparse.ArgumentParser(
135
152
  add_help=add_help,
136
153
  description="GitLab API Command Line Interface",
137
- formatter_class=VerticalHelpFormatter,
138
154
  allow_abbrev=False,
139
155
  )
140
156
  parser.add_argument("--version", help="Display the version.", action="store_true")
@@ -282,6 +298,16 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser:
282
298
  help=("GitLab CI job token [env var: CI_JOB_TOKEN]"),
283
299
  required=False,
284
300
  )
301
+ parser.add_argument(
302
+ "--skip-login",
303
+ help=(
304
+ "Skip initial authenticated API call to the current user endpoint. "
305
+ "This may be useful when invoking the CLI in scripts. "
306
+ "[env var: GITLAB_SKIP_LOGIN]"
307
+ ),
308
+ action="store_true",
309
+ default=os.getenv("GITLAB_SKIP_LOGIN"),
310
+ )
285
311
  return parser
286
312
 
287
313
 
@@ -368,6 +394,7 @@ def main() -> None:
368
394
  debug = args.debug
369
395
  gitlab_resource = args.gitlab_resource
370
396
  resource_action = args.resource_action
397
+ skip_login = args.skip_login
371
398
 
372
399
  args_dict = vars(args)
373
400
  # Remove CLI behavior-related args
@@ -390,6 +417,7 @@ def main() -> None:
390
417
  "private_token",
391
418
  "oauth_token",
392
419
  "job_token",
420
+ "skip_login",
393
421
  ):
394
422
  args_dict.pop(item)
395
423
  args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None}
@@ -398,7 +426,7 @@ def main() -> None:
398
426
  gl = gitlab.Gitlab.merge_config(vars(options), gitlab_id, config_files)
399
427
  if debug:
400
428
  gl.enable_debug()
401
- if gl.private_token or gl.oauth_token:
429
+ if not skip_login and (gl.private_token or gl.oauth_token):
402
430
  gl.auth()
403
431
  except Exception as e:
404
432
  die(str(e))
gitlab/client.py CHANGED
@@ -40,7 +40,6 @@ _PAGINATION_URL = (
40
40
 
41
41
 
42
42
  class Gitlab:
43
-
44
43
  """Represents a GitLab server connection.
45
44
 
46
45
  Args:
@@ -626,8 +625,8 @@ class Gitlab:
626
625
  for item in result.history:
627
626
  if item.status_code not in (301, 302):
628
627
  continue
629
- # GET methods can be redirected without issue
630
- if item.request.method == "GET":
628
+ # GET and HEAD methods can be redirected without issue
629
+ if item.request.method in ("GET", "HEAD"):
631
630
  continue
632
631
  target = item.headers.get("location")
633
632
  raise gitlab.exceptions.RedirectError(
gitlab/mixins.py CHANGED
@@ -550,7 +550,7 @@ class UserAgentDetailMixin(_RestObjectBase):
550
550
  _updated_attrs: Dict[str, Any]
551
551
  manager: base.RESTManager
552
552
 
553
- @cli.register_custom_action(("Snippet", "ProjectSnippet", "ProjectIssue"))
553
+ @cli.register_custom_action(cls_names=("Snippet", "ProjectSnippet", "ProjectIssue"))
554
554
  @exc.on_http_error(exc.GitlabGetError)
555
555
  def user_agent_detail(self, **kwargs: Any) -> Dict[str, Any]:
556
556
  """Get the user agent detail.
@@ -578,7 +578,8 @@ class AccessRequestMixin(_RestObjectBase):
578
578
  manager: base.RESTManager
579
579
 
580
580
  @cli.register_custom_action(
581
- ("ProjectAccessRequest", "GroupAccessRequest"), (), ("access_level",)
581
+ cls_names=("ProjectAccessRequest", "GroupAccessRequest"),
582
+ optional=("access_level",),
582
583
  )
583
584
  @exc.on_http_error(exc.GitlabUpdateError)
584
585
  def approve(
@@ -611,7 +612,7 @@ class DownloadMixin(_RestObjectBase):
611
612
  _updated_attrs: Dict[str, Any]
612
613
  manager: base.RESTManager
613
614
 
614
- @cli.register_custom_action(("GroupExport", "ProjectExport"))
615
+ @cli.register_custom_action(cls_names=("GroupExport", "ProjectExport"))
615
616
  @exc.on_http_error(exc.GitlabGetError)
616
617
  def download(
617
618
  self,
@@ -721,7 +722,7 @@ class SubscribableMixin(_RestObjectBase):
721
722
  manager: base.RESTManager
722
723
 
723
724
  @cli.register_custom_action(
724
- ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
725
+ cls_names=("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
725
726
  )
726
727
  @exc.on_http_error(exc.GitlabSubscribeError)
727
728
  def subscribe(self, **kwargs: Any) -> None:
@@ -741,7 +742,7 @@ class SubscribableMixin(_RestObjectBase):
741
742
  self._update_attrs(server_data)
742
743
 
743
744
  @cli.register_custom_action(
744
- ("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
745
+ cls_names=("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel")
745
746
  )
746
747
  @exc.on_http_error(exc.GitlabUnsubscribeError)
747
748
  def unsubscribe(self, **kwargs: Any) -> None:
@@ -769,7 +770,7 @@ class TodoMixin(_RestObjectBase):
769
770
  _updated_attrs: Dict[str, Any]
770
771
  manager: base.RESTManager
771
772
 
772
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
773
+ @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
773
774
  @exc.on_http_error(exc.GitlabTodoError)
774
775
  def todo(self, **kwargs: Any) -> None:
775
776
  """Create a todo associated to the object.
@@ -793,7 +794,7 @@ class TimeTrackingMixin(_RestObjectBase):
793
794
  _updated_attrs: Dict[str, Any]
794
795
  manager: base.RESTManager
795
796
 
796
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
797
+ @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
797
798
  @exc.on_http_error(exc.GitlabTimeTrackingError)
798
799
  def time_stats(self, **kwargs: Any) -> Dict[str, Any]:
799
800
  """Get time stats for the object.
@@ -819,7 +820,9 @@ class TimeTrackingMixin(_RestObjectBase):
819
820
  assert not isinstance(result, requests.Response)
820
821
  return result
821
822
 
822
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",))
823
+ @cli.register_custom_action(
824
+ cls_names=("ProjectIssue", "ProjectMergeRequest"), required=("duration",)
825
+ )
823
826
  @exc.on_http_error(exc.GitlabTimeTrackingError)
824
827
  def time_estimate(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
825
828
  """Set an estimated time of work for the object.
@@ -839,7 +842,7 @@ class TimeTrackingMixin(_RestObjectBase):
839
842
  assert not isinstance(result, requests.Response)
840
843
  return result
841
844
 
842
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
845
+ @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
843
846
  @exc.on_http_error(exc.GitlabTimeTrackingError)
844
847
  def reset_time_estimate(self, **kwargs: Any) -> Dict[str, Any]:
845
848
  """Resets estimated time for the object to 0 seconds.
@@ -857,7 +860,9 @@ class TimeTrackingMixin(_RestObjectBase):
857
860
  assert not isinstance(result, requests.Response)
858
861
  return result
859
862
 
860
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"), ("duration",))
863
+ @cli.register_custom_action(
864
+ cls_names=("ProjectIssue", "ProjectMergeRequest"), required=("duration",)
865
+ )
861
866
  @exc.on_http_error(exc.GitlabTimeTrackingError)
862
867
  def add_spent_time(self, duration: str, **kwargs: Any) -> Dict[str, Any]:
863
868
  """Add time spent working on the object.
@@ -877,7 +882,7 @@ class TimeTrackingMixin(_RestObjectBase):
877
882
  assert not isinstance(result, requests.Response)
878
883
  return result
879
884
 
880
- @cli.register_custom_action(("ProjectIssue", "ProjectMergeRequest"))
885
+ @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest"))
881
886
  @exc.on_http_error(exc.GitlabTimeTrackingError)
882
887
  def reset_spent_time(self, **kwargs: Any) -> Dict[str, Any]:
883
888
  """Resets the time spent working on the object.
@@ -904,7 +909,7 @@ class ParticipantsMixin(_RestObjectBase):
904
909
  _updated_attrs: Dict[str, Any]
905
910
  manager: base.RESTManager
906
911
 
907
- @cli.register_custom_action(("ProjectMergeRequest", "ProjectIssue"))
912
+ @cli.register_custom_action(cls_names=("ProjectMergeRequest", "ProjectIssue"))
908
913
  @exc.on_http_error(exc.GitlabListError)
909
914
  def participants(self, **kwargs: Any) -> Dict[str, Any]:
910
915
  """List the participants.
@@ -932,7 +937,8 @@ class ParticipantsMixin(_RestObjectBase):
932
937
 
933
938
  class BadgeRenderMixin(_RestManagerBase):
934
939
  @cli.register_custom_action(
935
- ("GroupBadgeManager", "ProjectBadgeManager"), ("link_url", "image_url")
940
+ cls_names=("GroupBadgeManager", "ProjectBadgeManager"),
941
+ required=("link_url", "image_url"),
936
942
  )
937
943
  @exc.on_http_error(exc.GitlabRenderError)
938
944
  def render(self, link_url: str, image_url: str, **kwargs: Any) -> Dict[str, Any]:
@@ -1025,7 +1031,9 @@ class UploadMixin(_RestObjectBase):
1025
1031
  data = self.attributes
1026
1032
  return self._upload_path.format(**data)
1027
1033
 
1028
- @cli.register_custom_action(("Project", "ProjectWiki"), ("filename", "filepath"))
1034
+ @cli.register_custom_action(
1035
+ cls_names=("Project", "ProjectWiki"), required=("filename", "filepath")
1036
+ )
1029
1037
  @exc.on_http_error(exc.GitlabUploadError)
1030
1038
  def upload(
1031
1039
  self,
gitlab/utils.py CHANGED
@@ -18,7 +18,8 @@ class _StdoutStream:
18
18
 
19
19
  def get_content_type(content_type: Optional[str]) -> str:
20
20
  message = email.message.Message()
21
- message["content-type"] = content_type
21
+ if content_type is not None:
22
+ message["content-type"] = content_type
22
23
 
23
24
  return message.get_content_type()
24
25
 
@@ -191,8 +192,7 @@ def warn(
191
192
  stacklevel = 1
192
193
  warning_from = ""
193
194
  for stacklevel, frame in enumerate(reversed(stack), start=1):
194
- if stacklevel == 2:
195
- warning_from = f" (python-gitlab: {frame.filename}:{frame.lineno})"
195
+ warning_from = f" (python-gitlab: {frame.filename}:{frame.lineno})"
196
196
  frame_dir = str(pathlib.Path(frame.filename).parent.resolve())
197
197
  if not frame_dir.startswith(str(pg_dir)):
198
198
  break
gitlab/v4/cli.py CHANGED
@@ -82,7 +82,7 @@ class GitlabCLI:
82
82
 
83
83
  def do_custom(self) -> Any:
84
84
  class_instance: Union[gitlab.base.RESTManager, gitlab.base.RESTObject]
85
- in_obj = cli.custom_actions[self.cls_name][self.resource_action][2]
85
+ in_obj = cli.custom_actions[self.cls_name][self.resource_action].in_object
86
86
 
87
87
  # Get the object (lazy), then act
88
88
  if in_obj:
@@ -207,12 +207,20 @@ def _populate_sub_parser_by_class(
207
207
  mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
208
208
 
209
209
  action_parsers: Dict[str, argparse.ArgumentParser] = {}
210
- for action_name in ["list", "get", "create", "update", "delete"]:
210
+ for action_name, help_text in [
211
+ ("list", "List the GitLab resources"),
212
+ ("get", "Get a GitLab resource"),
213
+ ("create", "Create a GitLab resource"),
214
+ ("update", "Update a GitLab resource"),
215
+ ("delete", "Delete a GitLab resource"),
216
+ ]:
211
217
  if not hasattr(mgr_cls, action_name):
212
218
  continue
213
219
 
214
220
  sub_parser_action = sub_parser.add_parser(
215
- action_name, conflict_handler="resolve"
221
+ action_name,
222
+ conflict_handler="resolve",
223
+ help=help_text,
216
224
  )
217
225
  action_parsers[action_name] = sub_parser_action
218
226
  sub_parser_action.add_argument("--sudo", required=False)
@@ -262,6 +270,10 @@ def _populate_sub_parser_by_class(
262
270
  sub_parser_action.add_argument(
263
271
  f"--{x.replace('_', '-')}", required=False
264
272
  )
273
+ if mgr_cls._create_attrs.exclusive:
274
+ group = sub_parser_action.add_mutually_exclusive_group()
275
+ for x in mgr_cls._create_attrs.exclusive:
276
+ group.add_argument(f"--{x.replace('_', '-')}")
265
277
 
266
278
  if action_name == "update":
267
279
  if cls._id_attr is not None:
@@ -280,6 +292,11 @@ def _populate_sub_parser_by_class(
280
292
  f"--{x.replace('_', '-')}", required=False
281
293
  )
282
294
 
295
+ if mgr_cls._update_attrs.exclusive:
296
+ group = sub_parser_action.add_mutually_exclusive_group()
297
+ for x in mgr_cls._update_attrs.exclusive:
298
+ group.add_argument(f"--{x.replace('_', '-')}")
299
+
283
300
  if cls.__name__ in cli.custom_actions:
284
301
  name = cls.__name__
285
302
  for action_name in cli.custom_actions[name]:
@@ -298,19 +315,19 @@ def _populate_sub_parser_by_class(
298
315
  )
299
316
  sub_parser_action.add_argument("--sudo", required=False)
300
317
 
318
+ custom_action = cli.custom_actions[name][action_name]
301
319
  # We need to get the object somehow
302
320
  if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin):
303
- if cls._id_attr is not None:
321
+ if cls._id_attr is not None and custom_action.requires_id:
304
322
  id_attr = cls._id_attr.replace("_", "-")
305
323
  sub_parser_action.add_argument(f"--{id_attr}", required=True)
306
324
 
307
- required, optional, dummy = cli.custom_actions[name][action_name]
308
- for x in required:
325
+ for x in custom_action.required:
309
326
  if x != cls._id_attr:
310
327
  sub_parser_action.add_argument(
311
328
  f"--{x.replace('_', '-')}", required=True
312
329
  )
313
- for x in optional:
330
+ for x in custom_action.optional:
314
331
  if x != cls._id_attr:
315
332
  sub_parser_action.add_argument(
316
333
  f"--{x.replace('_', '-')}", required=False
@@ -333,13 +350,13 @@ def _populate_sub_parser_by_class(
333
350
  )
334
351
  sub_parser_action.add_argument("--sudo", required=False)
335
352
 
336
- required, optional, dummy = cli.custom_actions[name][action_name]
337
- for x in required:
353
+ custom_action = cli.custom_actions[name][action_name]
354
+ for x in custom_action.required:
338
355
  if x != cls._id_attr:
339
356
  sub_parser_action.add_argument(
340
357
  f"--{x.replace('_', '-')}", required=True
341
358
  )
342
- for x in optional:
359
+ for x in custom_action.optional:
343
360
  if x != cls._id_attr:
344
361
  sub_parser_action.add_argument(
345
362
  f"--{x.replace('_', '-')}", required=False
@@ -365,8 +382,12 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
365
382
 
366
383
  for cls in sorted(classes, key=operator.attrgetter("__name__")):
367
384
  arg_name = cli.cls_to_gitlab_resource(cls)
385
+ mgr_cls_name = f"{cls.__name__}Manager"
386
+ mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
368
387
  object_group = subparsers.add_parser(
369
- arg_name, formatter_class=cli.VerticalHelpFormatter
388
+ arg_name,
389
+ formatter_class=cli.VerticalHelpFormatter,
390
+ help=f"API endpoint: {mgr_cls._path}",
370
391
  )
371
392
 
372
393
  object_subparsers = object_group.add_subparsers(
@@ -59,6 +59,7 @@ from .resource_groups import *
59
59
  from .reviewers import *
60
60
  from .runners import *
61
61
  from .secure_files import *
62
+ from .service_accounts import *
62
63
  from .settings import *
63
64
  from .sidekiq import *
64
65
  from .snippets import *
@@ -2,6 +2,7 @@
2
2
  GitLab API:
3
3
  https://docs.gitlab.com/ee/api/job_artifacts.html
4
4
  """
5
+
5
6
  from typing import Any, Callable, Iterator, Optional, TYPE_CHECKING, Union
6
7
 
7
8
  import requests
@@ -43,7 +44,9 @@ class ProjectArtifactManager(RESTManager):
43
44
  self.gitlab.http_delete(path, **kwargs)
44
45
 
45
46
  @cli.register_custom_action(
46
- "ProjectArtifactManager", ("ref_name", "job"), ("job_token",)
47
+ cls_names="ProjectArtifactManager",
48
+ required=("ref_name", "job"),
49
+ optional=("job_token",),
47
50
  )
48
51
  @exc.on_http_error(exc.GitlabGetError)
49
52
  def download(
@@ -61,7 +64,7 @@ class ProjectArtifactManager(RESTManager):
61
64
 
62
65
  Args:
63
66
  ref_name: Branch or tag name in repository. HEAD or SHA references
64
- are not supported.
67
+ are not supported.
65
68
  job: The name of the job.
66
69
  job_token: Job token for multi-project pipeline triggers.
67
70
  streamed: If True the data will be processed by chunks of
@@ -92,7 +95,8 @@ class ProjectArtifactManager(RESTManager):
92
95
  )
93
96
 
94
97
  @cli.register_custom_action(
95
- "ProjectArtifactManager", ("ref_name", "artifact_path", "job")
98
+ cls_names="ProjectArtifactManager",
99
+ required=("ref_name", "artifact_path", "job"),
96
100
  )
97
101
  @exc.on_http_error(exc.GitlabGetError)
98
102
  def raw(
@@ -2,6 +2,7 @@
2
2
  GitLab API:
3
3
  https://docs.gitlab.com/ee/api/audit_events.html
4
4
  """
5
+
5
6
  from typing import Any, cast, Union
6
7
 
7
8
  from gitlab.base import RESTManager, RESTObject
@@ -1,7 +1,13 @@
1
1
  from typing import Any, cast, Union
2
2
 
3
3
  from gitlab.base import RESTManager, RESTObject
4
- from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin
4
+ from gitlab.mixins import (
5
+ CRUDMixin,
6
+ NoUpdateMixin,
7
+ ObjectDeleteMixin,
8
+ SaveMixin,
9
+ UpdateMethod,
10
+ )
5
11
  from gitlab.types import RequiredOptional
6
12
 
7
13
  __all__ = [
@@ -28,11 +34,11 @@ class ProjectBranchManager(NoUpdateMixin, RESTManager):
28
34
  return cast(ProjectBranch, super().get(id=id, lazy=lazy, **kwargs))
29
35
 
30
36
 
31
- class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject):
37
+ class ProjectProtectedBranch(SaveMixin, ObjectDeleteMixin, RESTObject):
32
38
  _id_attr = "name"
33
39
 
34
40
 
35
- class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager):
41
+ class ProjectProtectedBranchManager(CRUDMixin, RESTManager):
36
42
  _path = "/projects/{project_id}/protected_branches"
37
43
  _obj_cls = ProjectProtectedBranch
38
44
  _from_parent_attrs = {"project_id": "id"}
@@ -49,6 +55,7 @@ class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager):
49
55
  "code_owner_approval_required",
50
56
  ),
51
57
  )
58
+ _update_method = UpdateMethod.PATCH
52
59
 
53
60
  def get(
54
61
  self, id: Union[str, int], lazy: bool = False, **kwargs: Any
@@ -31,8 +31,8 @@ class CiLintManager(CreateMixin, RESTManager):
31
31
  )
32
32
 
33
33
  @register_custom_action(
34
- "CiLintManager",
35
- ("content",),
34
+ cls_names="CiLintManager",
35
+ required=("content",),
36
36
  optional=("include_merged_yaml", "include_jobs"),
37
37
  )
38
38
  def validate(self, *args: Any, **kwargs: Any) -> None:
@@ -63,8 +63,8 @@ class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager):
63
63
  return cast(ProjectCiLint, super().get(**kwargs))
64
64
 
65
65
  @register_custom_action(
66
- "ProjectCiLintManager",
67
- ("content",),
66
+ cls_names="ProjectCiLintManager",
67
+ required=("content",),
68
68
  optional=("dry_run", "include_jobs", "ref"),
69
69
  )
70
70
  def validate(self, *args: Any, **kwargs: Any) -> None:
@@ -28,7 +28,7 @@ class ProjectCommit(RESTObject):
28
28
  discussions: ProjectCommitDiscussionManager
29
29
  statuses: "ProjectCommitStatusManager"
30
30
 
31
- @cli.register_custom_action("ProjectCommit")
31
+ @cli.register_custom_action(cls_names="ProjectCommit")
32
32
  @exc.on_http_error(exc.GitlabGetError)
33
33
  def diff(self, **kwargs: Any) -> Union[gitlab.GitlabList, List[Dict[str, Any]]]:
34
34
  """Generate the commit diff.
@@ -46,7 +46,7 @@ class ProjectCommit(RESTObject):
46
46
  path = f"{self.manager.path}/{self.encoded_id}/diff"
47
47
  return self.manager.gitlab.http_list(path, **kwargs)
48
48
 
49
- @cli.register_custom_action("ProjectCommit", ("branch",))
49
+ @cli.register_custom_action(cls_names="ProjectCommit", required=("branch",))
50
50
  @exc.on_http_error(exc.GitlabCherryPickError)
51
51
  def cherry_pick(self, branch: str, **kwargs: Any) -> None:
52
52
  """Cherry-pick a commit into a branch.
@@ -63,7 +63,7 @@ class ProjectCommit(RESTObject):
63
63
  post_data = {"branch": branch}
64
64
  self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
65
65
 
66
- @cli.register_custom_action("ProjectCommit", optional=("type",))
66
+ @cli.register_custom_action(cls_names="ProjectCommit", optional=("type",))
67
67
  @exc.on_http_error(exc.GitlabGetError)
68
68
  def refs(
69
69
  self, type: str = "all", **kwargs: Any
@@ -85,7 +85,7 @@ class ProjectCommit(RESTObject):
85
85
  query_data = {"type": type}
86
86
  return self.manager.gitlab.http_list(path, query_data=query_data, **kwargs)
87
87
 
88
- @cli.register_custom_action("ProjectCommit")
88
+ @cli.register_custom_action(cls_names="ProjectCommit")
89
89
  @exc.on_http_error(exc.GitlabGetError)
90
90
  def merge_requests(
91
91
  self, **kwargs: Any
@@ -105,7 +105,7 @@ class ProjectCommit(RESTObject):
105
105
  path = f"{self.manager.path}/{self.encoded_id}/merge_requests"
106
106
  return self.manager.gitlab.http_list(path, **kwargs)
107
107
 
108
- @cli.register_custom_action("ProjectCommit", ("branch",))
108
+ @cli.register_custom_action(cls_names="ProjectCommit", required=("branch",))
109
109
  @exc.on_http_error(exc.GitlabRevertError)
110
110
  def revert(
111
111
  self, branch: str, **kwargs: Any
@@ -127,7 +127,7 @@ class ProjectCommit(RESTObject):
127
127
  post_data = {"branch": branch}
128
128
  return self.manager.gitlab.http_post(path, post_data=post_data, **kwargs)
129
129
 
130
- @cli.register_custom_action("ProjectCommit")
130
+ @cli.register_custom_action(cls_names="ProjectCommit")
131
131
  @exc.on_http_error(exc.GitlabGetError)
132
132
  def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
133
133
  """Get the signature of the commit.
@@ -42,8 +42,8 @@ class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager):
42
42
  _path = "/projects/{project_id}/registry/repositories/{repository_id}/tags"
43
43
 
44
44
  @cli.register_custom_action(
45
- "ProjectRegistryTagManager",
46
- ("name_regex_delete",),
45
+ cls_names="ProjectRegistryTagManager",
46
+ required=("name_regex_delete",),
47
47
  optional=("keep_n", "name_regex_keep", "older_than"),
48
48
  )
49
49
  @exc.on_http_error(exc.GitlabDeleteError)
@@ -36,7 +36,9 @@ class ProjectKeyManager(CRUDMixin, RESTManager):
36
36
  _create_attrs = RequiredOptional(required=("title", "key"), optional=("can_push",))
37
37
  _update_attrs = RequiredOptional(optional=("title", "can_push"))
38
38
 
39
- @cli.register_custom_action("ProjectKeyManager", ("key_id",))
39
+ @cli.register_custom_action(
40
+ cls_names="ProjectKeyManager", required=("key_id",), requires_id=False
41
+ )
40
42
  @exc.on_http_error(exc.GitlabProjectDeployKeyError)
41
43
  def enable(
42
44
  self, key_id: int, **kwargs: Any
@@ -2,6 +2,7 @@
2
2
  GitLab API:
3
3
  https://docs.gitlab.com/ee/api/deployments.html
4
4
  """
5
+
5
6
  from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union
6
7
 
7
8
  from gitlab import cli
@@ -22,8 +23,8 @@ class ProjectDeployment(SaveMixin, RESTObject):
22
23
  mergerequests: ProjectDeploymentMergeRequestManager
23
24
 
24
25
  @cli.register_custom_action(
25
- "ProjectDeployment",
26
- mandatory=("status",),
26
+ cls_names="ProjectDeployment",
27
+ required=("status",),
27
28
  optional=("comment", "represented_as"),
28
29
  )
29
30
  @exc.on_http_error(exc.GitlabDeploymentApprovalError)
@@ -24,7 +24,7 @@ __all__ = [
24
24
 
25
25
 
26
26
  class ProjectEnvironment(SaveMixin, ObjectDeleteMixin, RESTObject):
27
- @cli.register_custom_action("ProjectEnvironment")
27
+ @cli.register_custom_action(cls_names="ProjectEnvironment")
28
28
  @exc.on_http_error(exc.GitlabStopError)
29
29
  def stop(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
30
30
  """Stop the environment.
@@ -2,6 +2,7 @@
2
2
  GitLab API:
3
3
  https://docs.gitlab.com/ee/api/features.html
4
4
  """
5
+
5
6
  from typing import Any, Optional, TYPE_CHECKING, Union
6
7
 
7
8
  from gitlab import exceptions as exc