python-gitlab 5.6.0__py3-none-any.whl → 6.0.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/__init__.py +0 -1
- gitlab/_backends/protocol.py +9 -13
- gitlab/_backends/requests_backend.py +12 -12
- gitlab/_version.py +1 -1
- gitlab/base.py +48 -48
- gitlab/cli.py +14 -24
- gitlab/client.py +114 -140
- gitlab/config.py +16 -17
- gitlab/exceptions.py +7 -5
- gitlab/mixins.py +154 -238
- gitlab/types.py +13 -14
- gitlab/utils.py +32 -43
- gitlab/v4/cli.py +50 -53
- gitlab/v4/objects/__init__.py +1 -0
- gitlab/v4/objects/access_requests.py +11 -3
- gitlab/v4/objects/appearance.py +12 -14
- gitlab/v4/objects/applications.py +5 -6
- gitlab/v4/objects/artifacts.py +10 -17
- gitlab/v4/objects/audit_events.py +4 -19
- gitlab/v4/objects/award_emojis.py +13 -57
- gitlab/v4/objects/badges.py +4 -19
- gitlab/v4/objects/boards.py +7 -27
- gitlab/v4/objects/branches.py +3 -15
- gitlab/v4/objects/broadcast_messages.py +3 -13
- gitlab/v4/objects/bulk_imports.py +6 -14
- gitlab/v4/objects/ci_lint.py +7 -13
- gitlab/v4/objects/cluster_agents.py +3 -13
- gitlab/v4/objects/clusters.py +13 -23
- gitlab/v4/objects/commits.py +23 -28
- gitlab/v4/objects/container_registry.py +13 -19
- gitlab/v4/objects/custom_attributes.py +16 -21
- gitlab/v4/objects/deploy_keys.py +22 -19
- gitlab/v4/objects/deploy_tokens.py +14 -32
- gitlab/v4/objects/deployments.py +13 -15
- gitlab/v4/objects/discussions.py +13 -29
- gitlab/v4/objects/draft_notes.py +4 -14
- gitlab/v4/objects/environments.py +13 -21
- gitlab/v4/objects/epics.py +14 -17
- gitlab/v4/objects/events.py +27 -79
- gitlab/v4/objects/export_import.py +7 -19
- gitlab/v4/objects/features.py +11 -12
- gitlab/v4/objects/files.py +23 -38
- gitlab/v4/objects/geo_nodes.py +7 -11
- gitlab/v4/objects/group_access_tokens.py +6 -13
- gitlab/v4/objects/groups.py +40 -37
- gitlab/v4/objects/hooks.py +4 -17
- gitlab/v4/objects/integrations.py +7 -18
- gitlab/v4/objects/invitations.py +12 -23
- gitlab/v4/objects/issues.py +21 -27
- gitlab/v4/objects/iterations.py +4 -8
- gitlab/v4/objects/job_token_scope.py +18 -14
- gitlab/v4/objects/jobs.py +17 -32
- gitlab/v4/objects/keys.py +8 -11
- gitlab/v4/objects/labels.py +19 -30
- gitlab/v4/objects/ldap.py +25 -9
- gitlab/v4/objects/member_roles.py +102 -0
- gitlab/v4/objects/members.py +11 -29
- gitlab/v4/objects/merge_request_approvals.py +31 -44
- gitlab/v4/objects/merge_requests.py +30 -40
- gitlab/v4/objects/merge_trains.py +3 -6
- gitlab/v4/objects/milestones.py +23 -29
- gitlab/v4/objects/namespaces.py +4 -10
- gitlab/v4/objects/notes.py +26 -69
- gitlab/v4/objects/notification_settings.py +5 -14
- gitlab/v4/objects/package_protection_rules.py +8 -8
- gitlab/v4/objects/packages.py +22 -37
- gitlab/v4/objects/pages.py +8 -14
- gitlab/v4/objects/personal_access_tokens.py +7 -10
- gitlab/v4/objects/pipelines.py +38 -47
- gitlab/v4/objects/project_access_tokens.py +6 -13
- gitlab/v4/objects/projects.py +54 -76
- gitlab/v4/objects/push_rules.py +13 -15
- gitlab/v4/objects/registry_protection_repository_rules.py +6 -7
- gitlab/v4/objects/registry_protection_rules.py +7 -11
- gitlab/v4/objects/releases.py +6 -20
- gitlab/v4/objects/repositories.py +25 -34
- gitlab/v4/objects/resource_groups.py +10 -15
- gitlab/v4/objects/reviewers.py +4 -2
- gitlab/v4/objects/runners.py +14 -13
- gitlab/v4/objects/secure_files.py +8 -21
- gitlab/v4/objects/service_accounts.py +7 -5
- gitlab/v4/objects/settings.py +13 -14
- gitlab/v4/objects/sidekiq.py +17 -18
- gitlab/v4/objects/snippets.py +78 -66
- gitlab/v4/objects/statistics.py +8 -23
- gitlab/v4/objects/status_checks.py +6 -3
- gitlab/v4/objects/tags.py +3 -13
- gitlab/v4/objects/templates.py +11 -59
- gitlab/v4/objects/todos.py +3 -6
- gitlab/v4/objects/topics.py +10 -21
- gitlab/v4/objects/triggers.py +3 -13
- gitlab/v4/objects/users.py +87 -93
- gitlab/v4/objects/variables.py +4 -19
- gitlab/v4/objects/wikis.py +4 -19
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info}/METADATA +3 -2
- python_gitlab-6.0.0.dist-info/RECORD +107 -0
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info}/WHEEL +1 -1
- python_gitlab-5.6.0.dist-info/RECORD +0 -106
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info}/entry_points.txt +0 -0
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info/licenses}/AUTHORS +0 -0
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info/licenses}/COPYING +0 -0
- {python_gitlab-5.6.0.dist-info → python_gitlab-6.0.0.dist-info}/top_level.txt +0 -0
gitlab/types.py
CHANGED
@@ -1,18 +1,17 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import dataclasses
|
2
|
-
from typing import Any,
|
4
|
+
from typing import Any, TYPE_CHECKING
|
3
5
|
|
4
6
|
|
5
7
|
@dataclasses.dataclass(frozen=True)
|
6
8
|
class RequiredOptional:
|
7
|
-
required:
|
8
|
-
optional:
|
9
|
-
exclusive:
|
9
|
+
required: tuple[str, ...] = ()
|
10
|
+
optional: tuple[str, ...] = ()
|
11
|
+
exclusive: tuple[str, ...] = ()
|
10
12
|
|
11
13
|
def validate_attrs(
|
12
|
-
self,
|
13
|
-
*,
|
14
|
-
data: Dict[str, Any],
|
15
|
-
excludes: Optional[List[str]] = None,
|
14
|
+
self, *, data: dict[str, Any], excludes: list[str] | None = None
|
16
15
|
) -> None:
|
17
16
|
if excludes is None:
|
18
17
|
excludes = []
|
@@ -46,7 +45,7 @@ class GitlabAttribute:
|
|
46
45
|
def set_from_cli(self, cli_value: Any) -> None:
|
47
46
|
self._value = cli_value
|
48
47
|
|
49
|
-
def get_for_api(self, *, key: str) ->
|
48
|
+
def get_for_api(self, *, key: str) -> tuple[str, Any]:
|
50
49
|
return (key, self._value)
|
51
50
|
|
52
51
|
|
@@ -59,7 +58,7 @@ class _ListArrayAttribute(GitlabAttribute):
|
|
59
58
|
else:
|
60
59
|
self._value = [item.strip() for item in cli_value.split(",")]
|
61
60
|
|
62
|
-
def get_for_api(self, *, key: str) ->
|
61
|
+
def get_for_api(self, *, key: str) -> tuple[str, str]:
|
63
62
|
# Do not comma-split single value passed as string
|
64
63
|
if isinstance(self._value, str):
|
65
64
|
return (key, self._value)
|
@@ -73,7 +72,7 @@ class ArrayAttribute(_ListArrayAttribute):
|
|
73
72
|
"""To support `array` types as documented in
|
74
73
|
https://docs.gitlab.com/ee/api/#array"""
|
75
74
|
|
76
|
-
def get_for_api(self, *, key: str) ->
|
75
|
+
def get_for_api(self, *, key: str) -> tuple[str, Any]:
|
77
76
|
if isinstance(self._value, str):
|
78
77
|
return (f"{key}[]", self._value)
|
79
78
|
|
@@ -89,17 +88,17 @@ class CommaSeparatedListAttribute(_ListArrayAttribute):
|
|
89
88
|
|
90
89
|
|
91
90
|
class LowercaseStringAttribute(GitlabAttribute):
|
92
|
-
def get_for_api(self, *, key: str) ->
|
91
|
+
def get_for_api(self, *, key: str) -> tuple[str, str]:
|
93
92
|
return (key, str(self._value).lower())
|
94
93
|
|
95
94
|
|
96
95
|
class FileAttribute(GitlabAttribute):
|
97
96
|
@staticmethod
|
98
|
-
def get_file_name(attr_name:
|
97
|
+
def get_file_name(attr_name: str | None = None) -> str | None:
|
99
98
|
return attr_name
|
100
99
|
|
101
100
|
|
102
101
|
class ImageAttribute(FileAttribute):
|
103
102
|
@staticmethod
|
104
|
-
def get_file_name(attr_name:
|
103
|
+
def get_file_name(attr_name: str | None = None) -> str:
|
105
104
|
return f"{attr_name}.png" if attr_name else "image.png"
|
gitlab/utils.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import dataclasses
|
2
4
|
import email.message
|
3
5
|
import logging
|
@@ -6,18 +8,8 @@ import time
|
|
6
8
|
import traceback
|
7
9
|
import urllib.parse
|
8
10
|
import warnings
|
9
|
-
from
|
10
|
-
|
11
|
-
Callable,
|
12
|
-
Dict,
|
13
|
-
Iterator,
|
14
|
-
Literal,
|
15
|
-
MutableMapping,
|
16
|
-
Optional,
|
17
|
-
Tuple,
|
18
|
-
Type,
|
19
|
-
Union,
|
20
|
-
)
|
11
|
+
from collections.abc import Iterator, MutableMapping
|
12
|
+
from typing import Any, Callable, Literal
|
21
13
|
|
22
14
|
import requests
|
23
15
|
|
@@ -29,7 +21,7 @@ class _StdoutStream:
|
|
29
21
|
print(chunk)
|
30
22
|
|
31
23
|
|
32
|
-
def get_base_url(url:
|
24
|
+
def get_base_url(url: str | None = None) -> str:
|
33
25
|
"""Return the base URL with the trailing slash stripped.
|
34
26
|
If the URL is a Falsy value, return the default URL.
|
35
27
|
Returns:
|
@@ -41,7 +33,7 @@ def get_base_url(url: Optional[str] = None) -> str:
|
|
41
33
|
return url.rstrip("/")
|
42
34
|
|
43
35
|
|
44
|
-
def get_content_type(content_type:
|
36
|
+
def get_content_type(content_type: str | None) -> str:
|
45
37
|
message = email.message.Message()
|
46
38
|
if content_type is not None:
|
47
39
|
message["content-type"] = content_type
|
@@ -54,11 +46,11 @@ class MaskingFormatter(logging.Formatter):
|
|
54
46
|
|
55
47
|
def __init__(
|
56
48
|
self,
|
57
|
-
fmt:
|
58
|
-
datefmt:
|
49
|
+
fmt: str | None = logging.BASIC_FORMAT,
|
50
|
+
datefmt: str | None = None,
|
59
51
|
style: Literal["%", "{", "$"] = "%",
|
60
52
|
validate: bool = True,
|
61
|
-
masked:
|
53
|
+
masked: str | None = None,
|
62
54
|
) -> None:
|
63
55
|
super().__init__(fmt, datefmt, style, validate)
|
64
56
|
self.masked = masked
|
@@ -77,11 +69,11 @@ class MaskingFormatter(logging.Formatter):
|
|
77
69
|
def response_content(
|
78
70
|
response: requests.Response,
|
79
71
|
streamed: bool,
|
80
|
-
action:
|
72
|
+
action: Callable[[bytes], Any] | None,
|
81
73
|
chunk_size: int,
|
82
74
|
*,
|
83
75
|
iterator: bool,
|
84
|
-
) ->
|
76
|
+
) -> bytes | Iterator[Any] | None:
|
85
77
|
if iterator:
|
86
78
|
return response.iter_content(chunk_size=chunk_size)
|
87
79
|
|
@@ -101,17 +93,15 @@ class Retry:
|
|
101
93
|
def __init__(
|
102
94
|
self,
|
103
95
|
max_retries: int,
|
104
|
-
obey_rate_limit:
|
105
|
-
retry_transient_errors:
|
96
|
+
obey_rate_limit: bool | None = True,
|
97
|
+
retry_transient_errors: bool | None = False,
|
106
98
|
) -> None:
|
107
99
|
self.cur_retries = 0
|
108
100
|
self.max_retries = max_retries
|
109
101
|
self.obey_rate_limit = obey_rate_limit
|
110
102
|
self.retry_transient_errors = retry_transient_errors
|
111
103
|
|
112
|
-
def _retryable_status_code(
|
113
|
-
self, status_code: Optional[int], reason: str = ""
|
114
|
-
) -> bool:
|
104
|
+
def _retryable_status_code(self, status_code: int | None, reason: str = "") -> bool:
|
115
105
|
if status_code == 429 and self.obey_rate_limit:
|
116
106
|
return True
|
117
107
|
|
@@ -126,8 +116,8 @@ class Retry:
|
|
126
116
|
|
127
117
|
def handle_retry_on_status(
|
128
118
|
self,
|
129
|
-
status_code:
|
130
|
-
headers:
|
119
|
+
status_code: int | None,
|
120
|
+
headers: MutableMapping[str, str] | None = None,
|
131
121
|
reason: str = "",
|
132
122
|
) -> bool:
|
133
123
|
if not self._retryable_status_code(status_code, reason):
|
@@ -163,12 +153,12 @@ class Retry:
|
|
163
153
|
|
164
154
|
|
165
155
|
def _transform_types(
|
166
|
-
data:
|
167
|
-
custom_types:
|
156
|
+
data: dict[str, Any],
|
157
|
+
custom_types: dict[str, Any],
|
168
158
|
*,
|
169
159
|
transform_data: bool,
|
170
|
-
transform_files:
|
171
|
-
) ->
|
160
|
+
transform_files: bool | None = True,
|
161
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
172
162
|
"""Copy the data dict with attributes that have custom types and transform them
|
173
163
|
before being sent to the server.
|
174
164
|
|
@@ -198,6 +188,12 @@ def _transform_types(
|
|
198
188
|
|
199
189
|
# if the type is FileAttribute we need to pass the data as file
|
200
190
|
if isinstance(gitlab_attribute, types.FileAttribute) and transform_files:
|
191
|
+
# The GitLab API accepts mixed types
|
192
|
+
# (e.g. a file for avatar image or empty string for removing the avatar)
|
193
|
+
# So if string is empty, keep it in data dict
|
194
|
+
if isinstance(data[attr_name], str) and data[attr_name] == "":
|
195
|
+
continue
|
196
|
+
|
201
197
|
key = gitlab_attribute.get_file_name(attr_name)
|
202
198
|
files[attr_name] = (key, data.pop(attr_name))
|
203
199
|
continue
|
@@ -214,11 +210,7 @@ def _transform_types(
|
|
214
210
|
return data, files
|
215
211
|
|
216
212
|
|
217
|
-
def copy_dict(
|
218
|
-
*,
|
219
|
-
src: Dict[str, Any],
|
220
|
-
dest: Dict[str, Any],
|
221
|
-
) -> None:
|
213
|
+
def copy_dict(*, src: dict[str, Any], dest: dict[str, Any]) -> None:
|
222
214
|
for k, v in src.items():
|
223
215
|
if isinstance(v, dict):
|
224
216
|
# NOTE(jlvillal): This provides some support for the `hash` type
|
@@ -247,7 +239,7 @@ class EncodedId(str):
|
|
247
239
|
https://docs.gitlab.com/ee/api/index.html#path-parameters
|
248
240
|
"""
|
249
241
|
|
250
|
-
def __new__(cls, value:
|
242
|
+
def __new__(cls, value: str | int | EncodedId) -> EncodedId:
|
251
243
|
if isinstance(value, EncodedId):
|
252
244
|
return value
|
253
245
|
|
@@ -258,15 +250,15 @@ class EncodedId(str):
|
|
258
250
|
return super().__new__(cls, value)
|
259
251
|
|
260
252
|
|
261
|
-
def remove_none_from_dict(data:
|
253
|
+
def remove_none_from_dict(data: dict[str, Any]) -> dict[str, Any]:
|
262
254
|
return {k: v for k, v in data.items() if v is not None}
|
263
255
|
|
264
256
|
|
265
257
|
def warn(
|
266
258
|
message: str,
|
267
259
|
*,
|
268
|
-
category:
|
269
|
-
source:
|
260
|
+
category: type[Warning] | None = None,
|
261
|
+
source: Any | None = None,
|
270
262
|
show_caller: bool = True,
|
271
263
|
) -> None:
|
272
264
|
"""This `warnings.warn` wrapper function attempts to show the location causing the
|
@@ -290,10 +282,7 @@ def warn(
|
|
290
282
|
if show_caller:
|
291
283
|
message += warning_from
|
292
284
|
warnings.warn(
|
293
|
-
message=message,
|
294
|
-
category=category,
|
295
|
-
stacklevel=stacklevel,
|
296
|
-
source=source,
|
285
|
+
message=message, category=category, stacklevel=stacklevel, source=source
|
297
286
|
)
|
298
287
|
|
299
288
|
|
gitlab/v4/cli.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import argparse
|
2
4
|
import json
|
3
5
|
import operator
|
4
6
|
import sys
|
5
|
-
from typing import Any,
|
7
|
+
from typing import Any, TYPE_CHECKING
|
6
8
|
|
7
9
|
import gitlab
|
8
10
|
import gitlab.base
|
@@ -17,9 +19,9 @@ class GitlabCLI:
|
|
17
19
|
gl: gitlab.Gitlab,
|
18
20
|
gitlab_resource: str,
|
19
21
|
resource_action: str,
|
20
|
-
args:
|
22
|
+
args: dict[str, str],
|
21
23
|
) -> None:
|
22
|
-
self.cls:
|
24
|
+
self.cls: type[gitlab.base.RESTObject] = cli.gitlab_resource_to_cls(
|
23
25
|
gitlab_resource, namespace=gitlab.v4.objects
|
24
26
|
)
|
25
27
|
self.cls_name = self.cls.__name__
|
@@ -27,26 +29,17 @@ class GitlabCLI:
|
|
27
29
|
self.resource_action = resource_action.lower()
|
28
30
|
self.gl = gl
|
29
31
|
self.args = args
|
30
|
-
self.parent_args:
|
31
|
-
self.mgr_cls:
|
32
|
-
Type[gitlab.mixins.CreateMixin],
|
33
|
-
Type[gitlab.mixins.DeleteMixin],
|
34
|
-
Type[gitlab.mixins.GetMixin],
|
35
|
-
Type[gitlab.mixins.GetWithoutIdMixin],
|
36
|
-
Type[gitlab.mixins.ListMixin],
|
37
|
-
Type[gitlab.mixins.UpdateMixin],
|
38
|
-
] = getattr(gitlab.v4.objects, f"{self.cls.__name__}Manager")
|
32
|
+
self.parent_args: dict[str, Any] = {}
|
33
|
+
self.mgr_cls: Any = getattr(gitlab.v4.objects, f"{self.cls.__name__}Manager")
|
39
34
|
# We could do something smart, like splitting the manager name to find
|
40
35
|
# parents, build the chain of managers to get to the final object.
|
41
36
|
# Instead we do something ugly and efficient: interpolate variables in
|
42
37
|
# the class _path attribute, and replace the value with the result.
|
43
|
-
if TYPE_CHECKING:
|
44
|
-
assert self.mgr_cls._path is not None
|
45
38
|
|
46
39
|
self._process_from_parent_attrs()
|
47
40
|
|
48
41
|
self.mgr_cls._path = self.mgr_cls._path.format(**self.parent_args)
|
49
|
-
self.mgr = self.mgr_cls(gl)
|
42
|
+
self.mgr: Any = self.mgr_cls(gl)
|
50
43
|
self.mgr._from_parent_attrs = self.parent_args
|
51
44
|
if self.mgr_cls._types:
|
52
45
|
for attr_name, type_cls in self.mgr_cls._types.items():
|
@@ -82,7 +75,9 @@ class GitlabCLI:
|
|
82
75
|
return self.do_custom()
|
83
76
|
|
84
77
|
def do_custom(self) -> Any:
|
85
|
-
class_instance:
|
78
|
+
class_instance: (
|
79
|
+
gitlab.base.RESTManager[gitlab.base.RESTObject] | gitlab.base.RESTObject
|
80
|
+
)
|
86
81
|
in_obj = cli.custom_actions[self.cls_name][self.resource_action].in_object
|
87
82
|
|
88
83
|
# Get the object (lazy), then act
|
@@ -132,13 +127,13 @@ class GitlabCLI:
|
|
132
127
|
assert isinstance(self.mgr, gitlab.mixins.CreateMixin)
|
133
128
|
try:
|
134
129
|
result = self.mgr.create(self.args)
|
130
|
+
if TYPE_CHECKING:
|
131
|
+
assert isinstance(result, gitlab.base.RESTObject)
|
135
132
|
except Exception as e: # pragma: no cover, cli.die is unit-tested
|
136
133
|
cli.die("Impossible to create object", e)
|
137
134
|
return result
|
138
135
|
|
139
|
-
def do_list(
|
140
|
-
self,
|
141
|
-
) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]:
|
136
|
+
def do_list(self) -> list[gitlab.base.RESTObject]:
|
142
137
|
if TYPE_CHECKING:
|
143
138
|
assert isinstance(self.mgr, gitlab.mixins.ListMixin)
|
144
139
|
message_details = gitlab.utils.WarnMessageData(
|
@@ -150,15 +145,19 @@ class GitlabCLI:
|
|
150
145
|
)
|
151
146
|
|
152
147
|
try:
|
153
|
-
result = self.mgr.list(
|
148
|
+
result = self.mgr.list(
|
149
|
+
**self.args, message_details=message_details, iterator=False
|
150
|
+
)
|
154
151
|
except Exception as e: # pragma: no cover, cli.die is unit-tested
|
155
152
|
cli.die("Impossible to list objects", e)
|
156
153
|
return result
|
157
154
|
|
158
|
-
def do_get(self) ->
|
155
|
+
def do_get(self) -> gitlab.base.RESTObject | None:
|
159
156
|
if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin):
|
160
157
|
try:
|
161
158
|
result = self.mgr.get(id=None, **self.args)
|
159
|
+
if TYPE_CHECKING:
|
160
|
+
assert isinstance(result, gitlab.base.RESTObject) or result is None
|
162
161
|
except Exception as e: # pragma: no cover, cli.die is unit-tested
|
163
162
|
cli.die("Impossible to get object", e)
|
164
163
|
return result
|
@@ -170,6 +169,8 @@ class GitlabCLI:
|
|
170
169
|
id = self.args.pop(self.cls._id_attr)
|
171
170
|
try:
|
172
171
|
result = self.mgr.get(id, lazy=False, **self.args)
|
172
|
+
if TYPE_CHECKING:
|
173
|
+
assert isinstance(result, gitlab.base.RESTObject) or result is None
|
173
174
|
except Exception as e: # pragma: no cover, cli.die is unit-tested
|
174
175
|
cli.die("Impossible to get object", e)
|
175
176
|
return result
|
@@ -184,7 +185,7 @@ class GitlabCLI:
|
|
184
185
|
except Exception as e: # pragma: no cover, cli.die is unit-tested
|
185
186
|
cli.die("Impossible to destroy object", e)
|
186
187
|
|
187
|
-
def do_update(self) ->
|
188
|
+
def do_update(self) -> dict[str, Any]:
|
188
189
|
if TYPE_CHECKING:
|
189
190
|
assert isinstance(self.mgr, gitlab.mixins.UpdateMixin)
|
190
191
|
if issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin):
|
@@ -209,13 +210,12 @@ else:
|
|
209
210
|
|
210
211
|
|
211
212
|
def _populate_sub_parser_by_class(
|
212
|
-
cls:
|
213
|
-
sub_parser: _SubparserType,
|
213
|
+
cls: type[gitlab.base.RESTObject], sub_parser: _SubparserType
|
214
214
|
) -> None:
|
215
215
|
mgr_cls_name = f"{cls.__name__}Manager"
|
216
216
|
mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
|
217
217
|
|
218
|
-
action_parsers:
|
218
|
+
action_parsers: dict[str, argparse.ArgumentParser] = {}
|
219
219
|
for action_name, help_text in [
|
220
220
|
("list", "List the GitLab resources"),
|
221
221
|
("get", "Get a GitLab resource"),
|
@@ -227,9 +227,7 @@ def _populate_sub_parser_by_class(
|
|
227
227
|
continue
|
228
228
|
|
229
229
|
sub_parser_action = sub_parser.add_parser(
|
230
|
-
action_name,
|
231
|
-
conflict_handler="resolve",
|
232
|
-
help=help_text,
|
230
|
+
action_name, conflict_handler="resolve", help=help_text
|
233
231
|
)
|
234
232
|
action_parsers[action_name] = sub_parser_action
|
235
233
|
sub_parser_action.add_argument("--sudo", required=False)
|
@@ -401,16 +399,20 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
401
399
|
if not isinstance(cls, type):
|
402
400
|
continue
|
403
401
|
if issubclass(cls, gitlab.base.RESTManager):
|
404
|
-
|
405
|
-
classes.add(cls._obj_cls)
|
402
|
+
classes.add(cls._obj_cls)
|
406
403
|
|
407
404
|
for cls in sorted(classes, key=operator.attrgetter("__name__")):
|
405
|
+
if cls is gitlab.base.RESTObject:
|
406
|
+
# Skip managers where _obj_cls is a plain RESTObject class
|
407
|
+
# Those managers do not actually manage any objects and
|
408
|
+
# can only be used to calls specific API paths.
|
409
|
+
continue
|
410
|
+
|
408
411
|
arg_name = cli.cls_to_gitlab_resource(cls)
|
409
412
|
mgr_cls_name = f"{cls.__name__}Manager"
|
410
413
|
mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
|
411
414
|
object_group = subparsers.add_parser(
|
412
|
-
arg_name,
|
413
|
-
help=f"API endpoint: {mgr_cls._path}",
|
415
|
+
arg_name, help=f"API endpoint: {mgr_cls._path}"
|
414
416
|
)
|
415
417
|
|
416
418
|
object_subparsers = object_group.add_subparsers(
|
@@ -425,8 +427,8 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
425
427
|
|
426
428
|
|
427
429
|
def get_dict(
|
428
|
-
obj:
|
429
|
-
) ->
|
430
|
+
obj: str | dict[str, Any] | gitlab.base.RESTObject, fields: list[str]
|
431
|
+
) -> str | dict[str, Any]:
|
430
432
|
if not isinstance(obj, gitlab.base.RESTObject):
|
431
433
|
return obj
|
432
434
|
|
@@ -437,13 +439,13 @@ def get_dict(
|
|
437
439
|
|
438
440
|
class JSONPrinter:
|
439
441
|
@staticmethod
|
440
|
-
def display(d:
|
442
|
+
def display(d: str | dict[str, Any], **_kwargs: Any) -> None:
|
441
443
|
print(json.dumps(d))
|
442
444
|
|
443
445
|
@staticmethod
|
444
446
|
def display_list(
|
445
|
-
data:
|
446
|
-
fields:
|
447
|
+
data: list[str | dict[str, Any] | gitlab.base.RESTObject],
|
448
|
+
fields: list[str],
|
447
449
|
**_kwargs: Any,
|
448
450
|
) -> None:
|
449
451
|
print(json.dumps([get_dict(obj, fields) for obj in data]))
|
@@ -451,7 +453,7 @@ class JSONPrinter:
|
|
451
453
|
|
452
454
|
class YAMLPrinter:
|
453
455
|
@staticmethod
|
454
|
-
def display(d:
|
456
|
+
def display(d: str | dict[str, Any], **_kwargs: Any) -> None:
|
455
457
|
try:
|
456
458
|
import yaml # noqa
|
457
459
|
|
@@ -465,8 +467,8 @@ class YAMLPrinter:
|
|
465
467
|
|
466
468
|
@staticmethod
|
467
469
|
def display_list(
|
468
|
-
data:
|
469
|
-
fields:
|
470
|
+
data: list[str | dict[str, Any] | gitlab.base.RESTObject],
|
471
|
+
fields: list[str],
|
470
472
|
**_kwargs: Any,
|
471
473
|
) -> None:
|
472
474
|
try:
|
@@ -486,14 +488,14 @@ class YAMLPrinter:
|
|
486
488
|
|
487
489
|
|
488
490
|
class LegacyPrinter:
|
489
|
-
def display(self, _d:
|
491
|
+
def display(self, _d: str | dict[str, Any], **kwargs: Any) -> None:
|
490
492
|
verbose = kwargs.get("verbose", False)
|
491
493
|
padding = kwargs.get("padding", 0)
|
492
|
-
obj:
|
494
|
+
obj: dict[str, Any] | gitlab.base.RESTObject | None = kwargs.get("obj")
|
493
495
|
if TYPE_CHECKING:
|
494
496
|
assert obj is not None
|
495
497
|
|
496
|
-
def display_dict(d:
|
498
|
+
def display_dict(d: dict[str, Any], padding: int) -> None:
|
497
499
|
for k in sorted(d.keys()):
|
498
500
|
v = d[k]
|
499
501
|
if isinstance(v, dict):
|
@@ -547,10 +549,7 @@ class LegacyPrinter:
|
|
547
549
|
)
|
548
550
|
|
549
551
|
def display_list(
|
550
|
-
self,
|
551
|
-
data: List[Union[str, gitlab.base.RESTObject]],
|
552
|
-
fields: List[str],
|
553
|
-
**kwargs: Any,
|
552
|
+
self, data: list[str | gitlab.base.RESTObject], fields: list[str], **kwargs: Any
|
554
553
|
) -> None:
|
555
554
|
verbose = kwargs.get("verbose", False)
|
556
555
|
for obj in data:
|
@@ -561,9 +560,7 @@ class LegacyPrinter:
|
|
561
560
|
print("")
|
562
561
|
|
563
562
|
|
564
|
-
PRINTERS:
|
565
|
-
str, Union[Type[JSONPrinter], Type[LegacyPrinter], Type[YAMLPrinter]]
|
566
|
-
] = {
|
563
|
+
PRINTERS: dict[str, type[JSONPrinter] | type[LegacyPrinter] | type[YAMLPrinter]] = {
|
567
564
|
"json": JSONPrinter,
|
568
565
|
"legacy": LegacyPrinter,
|
569
566
|
"yaml": YAMLPrinter,
|
@@ -574,10 +571,10 @@ def run(
|
|
574
571
|
gl: gitlab.Gitlab,
|
575
572
|
gitlab_resource: str,
|
576
573
|
resource_action: str,
|
577
|
-
args:
|
574
|
+
args: dict[str, Any],
|
578
575
|
verbose: bool,
|
579
576
|
output: str,
|
580
|
-
fields:
|
577
|
+
fields: list[str],
|
581
578
|
) -> None:
|
582
579
|
g_cli = GitlabCLI(
|
583
580
|
gl=gl,
|
@@ -587,7 +584,7 @@ def run(
|
|
587
584
|
)
|
588
585
|
data = g_cli.run()
|
589
586
|
|
590
|
-
printer:
|
587
|
+
printer: JSONPrinter | LegacyPrinter | YAMLPrinter = PRINTERS[output]()
|
591
588
|
|
592
589
|
if isinstance(data, dict):
|
593
590
|
printer.display(data, verbose=True, obj=data)
|
gitlab/v4/objects/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
from gitlab.base import
|
1
|
+
from gitlab.base import RESTObject
|
2
2
|
from gitlab.mixins import (
|
3
3
|
AccessRequestMixin,
|
4
4
|
CreateMixin,
|
@@ -19,7 +19,11 @@ class GroupAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject):
|
|
19
19
|
pass
|
20
20
|
|
21
21
|
|
22
|
-
class GroupAccessRequestManager(
|
22
|
+
class GroupAccessRequestManager(
|
23
|
+
ListMixin[GroupAccessRequest],
|
24
|
+
CreateMixin[GroupAccessRequest],
|
25
|
+
DeleteMixin[GroupAccessRequest],
|
26
|
+
):
|
23
27
|
_path = "/groups/{group_id}/access_requests"
|
24
28
|
_obj_cls = GroupAccessRequest
|
25
29
|
_from_parent_attrs = {"group_id": "id"}
|
@@ -29,7 +33,11 @@ class ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject):
|
|
29
33
|
pass
|
30
34
|
|
31
35
|
|
32
|
-
class ProjectAccessRequestManager(
|
36
|
+
class ProjectAccessRequestManager(
|
37
|
+
ListMixin[ProjectAccessRequest],
|
38
|
+
CreateMixin[ProjectAccessRequest],
|
39
|
+
DeleteMixin[ProjectAccessRequest],
|
40
|
+
):
|
33
41
|
_path = "/projects/{project_id}/access_requests"
|
34
42
|
_obj_cls = ProjectAccessRequest
|
35
43
|
_from_parent_attrs = {"project_id": "id"}
|
gitlab/v4/objects/appearance.py
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
2
4
|
|
3
5
|
from gitlab import exceptions as exc
|
4
|
-
from gitlab.base import
|
6
|
+
from gitlab.base import RESTObject
|
5
7
|
from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin
|
6
8
|
from gitlab.types import RequiredOptional
|
7
9
|
|
8
|
-
__all__ = [
|
9
|
-
"ApplicationAppearance",
|
10
|
-
"ApplicationAppearanceManager",
|
11
|
-
]
|
10
|
+
__all__ = ["ApplicationAppearance", "ApplicationAppearanceManager"]
|
12
11
|
|
13
12
|
|
14
13
|
class ApplicationAppearance(SaveMixin, RESTObject):
|
15
14
|
_id_attr = None
|
16
15
|
|
17
16
|
|
18
|
-
class ApplicationAppearanceManager(
|
17
|
+
class ApplicationAppearanceManager(
|
18
|
+
GetWithoutIdMixin[ApplicationAppearance], UpdateMixin[ApplicationAppearance]
|
19
|
+
):
|
19
20
|
_path = "/application/appearance"
|
20
21
|
_obj_cls = ApplicationAppearance
|
21
22
|
_update_attrs = RequiredOptional(
|
@@ -31,16 +32,16 @@ class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
|
|
31
32
|
"message_background_color",
|
32
33
|
"message_font_color",
|
33
34
|
"email_header_and_footer_enabled",
|
34
|
-
)
|
35
|
+
)
|
35
36
|
)
|
36
37
|
|
37
38
|
@exc.on_http_error(exc.GitlabUpdateError)
|
38
39
|
def update(
|
39
40
|
self,
|
40
|
-
id:
|
41
|
-
new_data:
|
41
|
+
id: str | int | None = None,
|
42
|
+
new_data: dict[str, Any] | None = None,
|
42
43
|
**kwargs: Any,
|
43
|
-
) ->
|
44
|
+
) -> dict[str, Any]:
|
44
45
|
"""Update an object on the server.
|
45
46
|
|
46
47
|
Args:
|
@@ -58,6 +59,3 @@ class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
|
|
58
59
|
new_data = new_data or {}
|
59
60
|
data = new_data.copy()
|
60
61
|
return super().update(id, data, **kwargs)
|
61
|
-
|
62
|
-
def get(self, **kwargs: Any) -> ApplicationAppearance:
|
63
|
-
return cast(ApplicationAppearance, super().get(**kwargs))
|
@@ -1,11 +1,8 @@
|
|
1
|
-
from gitlab.base import
|
1
|
+
from gitlab.base import RESTObject
|
2
2
|
from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin
|
3
3
|
from gitlab.types import RequiredOptional
|
4
4
|
|
5
|
-
__all__ = [
|
6
|
-
"Application",
|
7
|
-
"ApplicationManager",
|
8
|
-
]
|
5
|
+
__all__ = ["Application", "ApplicationManager"]
|
9
6
|
|
10
7
|
|
11
8
|
class Application(ObjectDeleteMixin, RESTObject):
|
@@ -13,7 +10,9 @@ class Application(ObjectDeleteMixin, RESTObject):
|
|
13
10
|
_repr_attr = "name"
|
14
11
|
|
15
12
|
|
16
|
-
class ApplicationManager(
|
13
|
+
class ApplicationManager(
|
14
|
+
ListMixin[Application], CreateMixin[Application], DeleteMixin[Application]
|
15
|
+
):
|
17
16
|
_path = "/applications"
|
18
17
|
_obj_cls = Application
|
19
18
|
_create_attrs = RequiredOptional(
|