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.
- gitlab/_backends/protocol.py +2 -4
- gitlab/_version.py +1 -1
- gitlab/cli.py +33 -5
- gitlab/client.py +2 -3
- gitlab/mixins.py +22 -14
- gitlab/utils.py +3 -3
- gitlab/v4/cli.py +32 -11
- gitlab/v4/objects/__init__.py +1 -0
- gitlab/v4/objects/artifacts.py +7 -3
- gitlab/v4/objects/audit_events.py +1 -0
- gitlab/v4/objects/branches.py +10 -3
- gitlab/v4/objects/ci_lint.py +4 -4
- gitlab/v4/objects/commits.py +6 -6
- gitlab/v4/objects/container_registry.py +2 -2
- gitlab/v4/objects/deploy_keys.py +3 -1
- gitlab/v4/objects/deployments.py +3 -2
- gitlab/v4/objects/environments.py +1 -1
- gitlab/v4/objects/features.py +1 -0
- gitlab/v4/objects/files.py +15 -7
- gitlab/v4/objects/geo_nodes.py +4 -4
- gitlab/v4/objects/groups.py +13 -7
- gitlab/v4/objects/integrations.py +3 -1
- gitlab/v4/objects/issues.py +6 -4
- gitlab/v4/objects/iterations.py +29 -2
- gitlab/v4/objects/job_token_scope.py +48 -0
- gitlab/v4/objects/jobs.py +15 -15
- gitlab/v4/objects/merge_request_approvals.py +12 -59
- gitlab/v4/objects/merge_requests.py +14 -12
- gitlab/v4/objects/milestones.py +4 -4
- gitlab/v4/objects/namespaces.py +3 -1
- gitlab/v4/objects/packages.py +4 -4
- gitlab/v4/objects/pipelines.py +6 -5
- gitlab/v4/objects/projects.py +22 -18
- gitlab/v4/objects/repositories.py +14 -9
- gitlab/v4/objects/runners.py +2 -2
- gitlab/v4/objects/secure_files.py +2 -1
- gitlab/v4/objects/service_accounts.py +18 -0
- gitlab/v4/objects/sidekiq.py +4 -4
- gitlab/v4/objects/snippets.py +3 -3
- gitlab/v4/objects/todos.py +2 -2
- gitlab/v4/objects/topics.py +2 -2
- gitlab/v4/objects/users.py +11 -10
- gitlab/v4/objects/variables.py +1 -0
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/METADATA +20 -3
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/RECORD +50 -49
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/WHEEL +1 -1
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/AUTHORS +0 -0
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/COPYING +0 -0
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/entry_points.txt +0 -0
- {python_gitlab-4.4.0.dist-info → python_gitlab-4.6.0.dist-info}/top_level.txt +0 -0
gitlab/_backends/protocol.py
CHANGED
@@ -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
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,
|
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
|
-
|
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] = (
|
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
|
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"),
|
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(
|
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(
|
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"),
|
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(
|
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
|
-
|
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
|
-
|
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]
|
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 [
|
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,
|
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
|
-
|
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
|
-
|
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,
|
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(
|
gitlab/v4/objects/__init__.py
CHANGED
gitlab/v4/objects/artifacts.py
CHANGED
@@ -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",
|
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
|
-
|
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",
|
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(
|
gitlab/v4/objects/branches.py
CHANGED
@@ -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
|
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(
|
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
|
gitlab/v4/objects/ci_lint.py
CHANGED
@@ -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:
|
gitlab/v4/objects/commits.py
CHANGED
@@ -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)
|
gitlab/v4/objects/deploy_keys.py
CHANGED
@@ -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(
|
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
|
gitlab/v4/objects/deployments.py
CHANGED
@@ -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
|
-
|
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.
|