python-gitlab 4.6.0__py3-none-any.whl → 4.8.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/_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.6.0"
6
+ __version__ = "4.8.0"
gitlab/cli.py CHANGED
@@ -5,13 +5,13 @@ import os
5
5
  import pathlib
6
6
  import re
7
7
  import sys
8
- import textwrap
9
8
  from types import ModuleType
10
9
  from typing import (
11
10
  Any,
12
11
  Callable,
13
12
  cast,
14
13
  Dict,
14
+ NoReturn,
15
15
  Optional,
16
16
  Tuple,
17
17
  Type,
@@ -37,11 +37,12 @@ class CustomAction:
37
37
  optional: Tuple[str, ...]
38
38
  in_object: bool
39
39
  requires_id: bool # if the `_id_attr` value should be a required argument
40
+ help: Optional[str] # help text for the custom action
40
41
 
41
42
 
42
43
  # custom_actions = {
43
44
  # cls: {
44
- # action: (mandatory_args, optional_args, in_obj),
45
+ # action: CustomAction,
45
46
  # },
46
47
  # }
47
48
  custom_actions: Dict[str, Dict[str, CustomAction]] = {}
@@ -54,33 +55,6 @@ custom_actions: Dict[str, Dict[str, CustomAction]] = {}
54
55
  __F = TypeVar("__F", bound=Callable[..., Any])
55
56
 
56
57
 
57
- class VerticalHelpFormatter(argparse.HelpFormatter):
58
- def format_help(self) -> str:
59
- result = super().format_help()
60
- output = ""
61
- indent = self._indent_increment * " "
62
- for line in result.splitlines(keepends=True):
63
- # All of our resources are on one line and wrapped inside braces.
64
- # For example: {application,resource1,resource2}
65
- # except if there are fewer resources - then the line and help text
66
- # are collapsed on the same line.
67
- # For example: {list} Action to execute on the GitLab resource.
68
- # We then put each resource on its own line to make it easier to read.
69
- if line.strip().startswith("{"):
70
- choice_string, help_string = line.split("}", 1)
71
- choice_list = choice_string.strip(" {").split(",")
72
- help_string = help_string.strip()
73
-
74
- if help_string:
75
- help_indent = len(max(choice_list, key=len)) * " "
76
- choice_list.append(f"{help_indent} {help_string}")
77
-
78
- choices = "\n".join(choice_list)
79
- line = f"{textwrap.indent(choices, indent)}\n"
80
- output += line
81
- return output
82
-
83
-
84
58
  def register_custom_action(
85
59
  *,
86
60
  cls_names: Union[str, Tuple[str, ...]],
@@ -88,6 +62,7 @@ def register_custom_action(
88
62
  optional: Tuple[str, ...] = (),
89
63
  custom_action: Optional[str] = None,
90
64
  requires_id: bool = True, # if the `_id_attr` value should be a required argument
65
+ help: Optional[str] = None, # help text for the action
91
66
  ) -> Callable[[__F], __F]:
92
67
  def wrap(f: __F) -> __F:
93
68
  @functools.wraps(f)
@@ -115,6 +90,7 @@ def register_custom_action(
115
90
  optional=optional,
116
91
  in_object=in_obj,
117
92
  requires_id=requires_id,
93
+ help=help,
118
94
  )
119
95
 
120
96
  return cast(__F, wrapped_f)
@@ -122,7 +98,7 @@ def register_custom_action(
122
98
  return wrap
123
99
 
124
100
 
125
- def die(msg: str, e: Optional[Exception] = None) -> None:
101
+ def die(msg: str, e: Optional[Exception] = None) -> NoReturn:
126
102
  if e:
127
103
  msg = f"{msg} ({e})"
128
104
  sys.stderr.write(f"{msg}\n")
@@ -308,6 +284,12 @@ def _get_base_parser(add_help: bool = True) -> argparse.ArgumentParser:
308
284
  action="store_true",
309
285
  default=os.getenv("GITLAB_SKIP_LOGIN"),
310
286
  )
287
+ parser.add_argument(
288
+ "--no-mask-credentials",
289
+ help="Don't mask credentials in debug mode",
290
+ dest="mask_credentials",
291
+ action="store_false",
292
+ )
311
293
  return parser
312
294
 
313
295
 
@@ -395,29 +377,31 @@ def main() -> None:
395
377
  gitlab_resource = args.gitlab_resource
396
378
  resource_action = args.resource_action
397
379
  skip_login = args.skip_login
380
+ mask_credentials = args.mask_credentials
398
381
 
399
382
  args_dict = vars(args)
400
383
  # Remove CLI behavior-related args
401
384
  for item in (
402
- "gitlab",
385
+ "api_version",
403
386
  "config_file",
404
- "verbose",
405
387
  "debug",
388
+ "fields",
389
+ "gitlab",
406
390
  "gitlab_resource",
407
- "resource_action",
408
- "version",
391
+ "job_token",
392
+ "mask_credentials",
393
+ "oauth_token",
409
394
  "output",
410
- "fields",
395
+ "pagination",
396
+ "private_token",
397
+ "resource_action",
411
398
  "server_url",
399
+ "skip_login",
412
400
  "ssl_verify",
413
401
  "timeout",
414
- "api_version",
415
- "pagination",
416
402
  "user_agent",
417
- "private_token",
418
- "oauth_token",
419
- "job_token",
420
- "skip_login",
403
+ "verbose",
404
+ "version",
421
405
  ):
422
406
  args_dict.pop(item)
423
407
  args_dict = {k: _parse_value(v) for k, v in args_dict.items() if v is not None}
@@ -425,7 +409,7 @@ def main() -> None:
425
409
  try:
426
410
  gl = gitlab.Gitlab.merge_config(vars(options), gitlab_id, config_files)
427
411
  if debug:
428
- gl.enable_debug()
412
+ gl.enable_debug(mask_credentials=mask_credentials)
429
413
  if not skip_login and (gl.private_token or gl.oauth_token):
430
414
  gl.auth()
431
415
  except Exception as e:
gitlab/client.py CHANGED
@@ -869,6 +869,7 @@ class Gitlab:
869
869
  query_data: Optional[Dict[str, Any]] = None,
870
870
  *,
871
871
  iterator: Optional[bool] = None,
872
+ message_details: Optional[utils.WarnMessageData] = None,
872
873
  **kwargs: Any,
873
874
  ) -> Union["GitlabList", List[Dict[str, Any]]]:
874
875
  """Make a GET request to the Gitlab server for list-oriented queries.
@@ -952,16 +953,29 @@ class Gitlab:
952
953
  # Warn the user that they are only going to retrieve `per_page`
953
954
  # maximum items. This is a common cause of issues filed.
954
955
  total_items = "many" if gl_list.total is None else gl_list.total
955
- utils.warn(
956
- message=(
956
+ if message_details is not None:
957
+ message = message_details.message.format_map(
958
+ {
959
+ "len_items": len(items),
960
+ "per_page": gl_list.per_page,
961
+ "total_items": total_items,
962
+ }
963
+ )
964
+ show_caller = message_details.show_caller
965
+ else:
966
+ message = (
957
967
  f"Calling a `list()` method without specifying `get_all=True` or "
958
968
  f"`iterator=True` will return a maximum of {gl_list.per_page} items. "
959
969
  f"Your query returned {len(items)} of {total_items} items. See "
960
970
  f"{_PAGINATION_URL} for more details. If this was done intentionally, "
961
971
  f"then this warning can be supressed by adding the argument "
962
972
  f"`get_all=False` to the `list()` call."
963
- ),
973
+ )
974
+ show_caller = True
975
+ utils.warn(
976
+ message=message,
964
977
  category=UserWarning,
978
+ show_caller=show_caller,
965
979
  )
966
980
  return items
967
981
 
gitlab/mixins.py CHANGED
@@ -911,7 +911,9 @@ class ParticipantsMixin(_RestObjectBase):
911
911
 
912
912
  @cli.register_custom_action(cls_names=("ProjectMergeRequest", "ProjectIssue"))
913
913
  @exc.on_http_error(exc.GitlabListError)
914
- def participants(self, **kwargs: Any) -> Dict[str, Any]:
914
+ def participants(
915
+ self, **kwargs: Any
916
+ ) -> Union[gitlab.client.GitlabList, List[Dict[str, Any]]]:
915
917
  """List the participants.
916
918
 
917
919
  Args:
@@ -929,7 +931,7 @@ class ParticipantsMixin(_RestObjectBase):
929
931
  """
930
932
 
931
933
  path = f"{self.manager.path}/{self.encoded_id}/participants"
932
- result = self.manager.gitlab.http_get(path, **kwargs)
934
+ result = self.manager.gitlab.http_list(path, **kwargs)
933
935
  if TYPE_CHECKING:
934
936
  assert not isinstance(result, requests.Response)
935
937
  return result
gitlab/utils.py CHANGED
@@ -1,3 +1,4 @@
1
+ import dataclasses
1
2
  import email.message
2
3
  import logging
3
4
  import pathlib
@@ -177,6 +178,7 @@ def warn(
177
178
  *,
178
179
  category: Optional[Type[Warning]] = None,
179
180
  source: Optional[Any] = None,
181
+ show_caller: bool = True,
180
182
  ) -> None:
181
183
  """This `warnings.warn` wrapper function attempts to show the location causing the
182
184
  warning in the user code that called the library.
@@ -196,9 +198,17 @@ def warn(
196
198
  frame_dir = str(pathlib.Path(frame.filename).parent.resolve())
197
199
  if not frame_dir.startswith(str(pg_dir)):
198
200
  break
201
+ if show_caller:
202
+ message += warning_from
199
203
  warnings.warn(
200
- message=message + warning_from,
204
+ message=message,
201
205
  category=category,
202
206
  stacklevel=stacklevel,
203
207
  source=source,
204
208
  )
209
+
210
+
211
+ @dataclasses.dataclass
212
+ class WarnMessageData:
213
+ message: str
214
+ show_caller: bool
gitlab/v4/cli.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import json
2
3
  import operator
3
4
  import sys
4
5
  from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING, Union
@@ -140,8 +141,16 @@ class GitlabCLI:
140
141
  ) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]:
141
142
  if TYPE_CHECKING:
142
143
  assert isinstance(self.mgr, gitlab.mixins.ListMixin)
144
+ message_details = gitlab.utils.WarnMessageData(
145
+ message=(
146
+ "Your query returned {len_items} of {total_items} items. To return all "
147
+ "items use `--get-all`. To silence this warning use `--no-get-all`."
148
+ ),
149
+ show_caller=False,
150
+ )
151
+
143
152
  try:
144
- result = self.mgr.list(**self.args)
153
+ result = self.mgr.list(**self.args, message_details=message_details)
145
154
  except Exception as e: # pragma: no cover, cli.die is unit-tested
146
155
  cli.die("Impossible to list objects", e)
147
156
  return result
@@ -238,12 +247,25 @@ def _populate_sub_parser_by_class(
238
247
 
239
248
  sub_parser_action.add_argument("--page", required=False, type=int)
240
249
  sub_parser_action.add_argument("--per-page", required=False, type=int)
241
- sub_parser_action.add_argument(
250
+ get_all_group = sub_parser_action.add_mutually_exclusive_group()
251
+ get_all_group.add_argument(
242
252
  "--get-all",
243
253
  required=False,
244
- action="store_true",
254
+ action="store_const",
255
+ const=True,
256
+ default=None,
257
+ dest="get_all",
245
258
  help="Return all items from the server, without pagination.",
246
259
  )
260
+ get_all_group.add_argument(
261
+ "--no-get-all",
262
+ required=False,
263
+ action="store_const",
264
+ const=False,
265
+ default=None,
266
+ dest="get_all",
267
+ help="Don't return all items from the server.",
268
+ )
247
269
 
248
270
  if action_name == "delete":
249
271
  if cls._id_attr is not None:
@@ -300,11 +322,14 @@ def _populate_sub_parser_by_class(
300
322
  if cls.__name__ in cli.custom_actions:
301
323
  name = cls.__name__
302
324
  for action_name in cli.custom_actions[name]:
325
+ custom_action = cli.custom_actions[name][action_name]
303
326
  # NOTE(jlvillal): If we put a function for the `default` value of
304
327
  # the `get` it will always get called, which will break things.
305
328
  action_parser = action_parsers.get(action_name)
306
329
  if action_parser is None:
307
- sub_parser_action = sub_parser.add_parser(action_name)
330
+ sub_parser_action = sub_parser.add_parser(
331
+ action_name, help=custom_action.help
332
+ )
308
333
  else:
309
334
  sub_parser_action = action_parser
310
335
  # Get the attributes for URL/path construction
@@ -315,7 +340,6 @@ def _populate_sub_parser_by_class(
315
340
  )
316
341
  sub_parser_action.add_argument("--sudo", required=False)
317
342
 
318
- custom_action = cli.custom_actions[name][action_name]
319
343
  # We need to get the object somehow
320
344
  if not issubclass(cls, gitlab.mixins.GetWithoutIdMixin):
321
345
  if cls._id_attr is not None and custom_action.requires_id:
@@ -386,7 +410,6 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
386
410
  mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
387
411
  object_group = subparsers.add_parser(
388
412
  arg_name,
389
- formatter_class=cli.VerticalHelpFormatter,
390
413
  help=f"API endpoint: {mgr_cls._path}",
391
414
  )
392
415
 
@@ -415,8 +438,6 @@ def get_dict(
415
438
  class JSONPrinter:
416
439
  @staticmethod
417
440
  def display(d: Union[str, Dict[str, Any]], **_kwargs: Any) -> None:
418
- import json # noqa
419
-
420
441
  print(json.dumps(d))
421
442
 
422
443
  @staticmethod
@@ -425,8 +446,6 @@ class JSONPrinter:
425
446
  fields: List[str],
426
447
  **_kwargs: Any,
427
448
  ) -> None:
428
- import json # noqa
429
-
430
449
  print(json.dumps([get_dict(obj, fields) for obj in data]))
431
450
 
432
451
 
@@ -10,6 +10,7 @@ from .branches import *
10
10
  from .broadcast_messages import *
11
11
  from .bulk_imports import *
12
12
  from .ci_lint import *
13
+ from .cluster_agents import *
13
14
  from .clusters import *
14
15
  from .commits import *
15
16
  from .container_registry import *
@@ -46,6 +47,7 @@ from .milestones import *
46
47
  from .namespaces import *
47
48
  from .notes import *
48
49
  from .notification_settings import *
50
+ from .package_protection_rules import *
49
51
  from .packages import *
50
52
  from .pages import *
51
53
  from .personal_access_tokens import *
@@ -53,6 +55,7 @@ from .pipelines import *
53
55
  from .project_access_tokens import *
54
56
  from .projects import *
55
57
  from .push_rules import *
58
+ from .registry_protection_rules import *
56
59
  from .releases import *
57
60
  from .repositories import *
58
61
  from .resource_groups import *
@@ -0,0 +1,26 @@
1
+ from typing import Any, cast, Union
2
+
3
+ from gitlab.base import RESTManager, RESTObject
4
+ from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin, SaveMixin
5
+ from gitlab.types import RequiredOptional
6
+
7
+ __all__ = [
8
+ "ProjectClusterAgent",
9
+ "ProjectClusterAgentManager",
10
+ ]
11
+
12
+
13
+ class ProjectClusterAgent(SaveMixin, ObjectDeleteMixin, RESTObject):
14
+ _repr_attr = "name"
15
+
16
+
17
+ class ProjectClusterAgentManager(NoUpdateMixin, RESTManager):
18
+ _path = "/projects/{project_id}/cluster_agents"
19
+ _obj_cls = ProjectClusterAgent
20
+ _from_parent_attrs = {"project_id": "id"}
21
+ _create_attrs = RequiredOptional(required=("name",))
22
+
23
+ def get(
24
+ self, id: Union[str, int], lazy: bool = False, **kwargs: Any
25
+ ) -> ProjectClusterAgent:
26
+ return cast(ProjectClusterAgent, super().get(id=id, lazy=lazy, **kwargs))
@@ -127,6 +127,24 @@ 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(cls_names="ProjectCommit")
131
+ @exc.on_http_error(exc.GitlabGetError)
132
+ def sequence(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
133
+ """Get the sequence number of the commit.
134
+
135
+ Args:
136
+ **kwargs: Extra options to send to the server (e.g. sudo)
137
+
138
+ Raises:
139
+ GitlabAuthenticationError: If authentication is not correct
140
+ GitlabGetError: If the sequence number could not be retrieved
141
+
142
+ Returns:
143
+ The commit's sequence number
144
+ """
145
+ path = f"{self.manager.path}/{self.encoded_id}/sequence"
146
+ return self.manager.gitlab.http_get(path, **kwargs)
147
+
130
148
  @cli.register_custom_action(cls_names="ProjectCommit")
131
149
  @exc.on_http_error(exc.GitlabGetError)
132
150
  def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]:
@@ -37,7 +37,10 @@ class ProjectKeyManager(CRUDMixin, RESTManager):
37
37
  _update_attrs = RequiredOptional(optional=("title", "can_push"))
38
38
 
39
39
  @cli.register_custom_action(
40
- cls_names="ProjectKeyManager", required=("key_id",), requires_id=False
40
+ cls_names="ProjectKeyManager",
41
+ required=("key_id",),
42
+ requires_id=False,
43
+ help="Enable a deploy key for the project",
41
44
  )
42
45
  @exc.on_http_error(exc.GitlabProjectDeployKeyError)
43
46
  def enable(
@@ -228,13 +228,14 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
228
228
  self.gitlab.http_delete(path, query_data=data, **kwargs)
229
229
 
230
230
  @cli.register_custom_action(
231
- cls_names="ProjectFileManager", required=("file_path", "ref")
231
+ cls_names="ProjectFileManager",
232
+ required=("file_path",),
232
233
  )
233
234
  @exc.on_http_error(exc.GitlabGetError)
234
235
  def raw(
235
236
  self,
236
237
  file_path: str,
237
- ref: str,
238
+ ref: Optional[str] = None,
238
239
  streamed: bool = False,
239
240
  action: Optional[Callable[..., Any]] = None,
240
241
  chunk_size: int = 1024,
@@ -245,16 +246,16 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
245
246
  """Return the content of a file for a commit.
246
247
 
247
248
  Args:
248
- ref: ID of the commit
249
249
  file_path: Path of the file to return
250
+ ref: ID of the commit
250
251
  streamed: If True the data will be processed by chunks of
251
252
  `chunk_size` and each chunk is passed to `action` for
252
253
  treatment
253
- iterator: If True directly return the underlying response
254
- iterator
255
- action: Callable responsible of dealing with chunk of
254
+ action: Callable responsible for dealing with each chunk of
256
255
  data
257
256
  chunk_size: Size of each chunk
257
+ iterator: If True directly return the underlying response
258
+ iterator
258
259
  **kwargs: Extra options to send to the server (e.g. sudo)
259
260
 
260
261
  Raises:
@@ -266,7 +267,10 @@ class ProjectFileManager(GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTMa
266
267
  """
267
268
  file_path = utils.EncodedId(file_path)
268
269
  path = f"{self.path}/{file_path}/raw"
269
- query_data = {"ref": ref}
270
+ if ref is not None:
271
+ query_data = {"ref": ref}
272
+ else:
273
+ query_data = None
270
274
  result = self.gitlab.http_get(
271
275
  path, query_data=query_data, streamed=streamed, raw=True, **kwargs
272
276
  )
@@ -1,6 +1,8 @@
1
- from typing import Any, cast, Dict, Optional, Tuple, TYPE_CHECKING, Union
1
+ from typing import Any, cast, Dict, List, Optional, Tuple, TYPE_CHECKING, Union
2
2
 
3
- from gitlab import cli
3
+ import requests
4
+
5
+ from gitlab import cli, client
4
6
  from gitlab import exceptions as exc
5
7
  from gitlab import types
6
8
  from gitlab.base import RESTManager, RESTObject
@@ -182,7 +184,9 @@ class ProjectIssue(
182
184
 
183
185
  @cli.register_custom_action(cls_names="ProjectIssue")
184
186
  @exc.on_http_error(exc.GitlabGetError)
185
- def related_merge_requests(self, **kwargs: Any) -> Dict[str, Any]:
187
+ def related_merge_requests(
188
+ self, **kwargs: Any
189
+ ) -> Union[client.GitlabList, List[Dict[str, Any]]]:
186
190
  """List merge requests related to the issue.
187
191
 
188
192
  Args:
@@ -196,14 +200,16 @@ class ProjectIssue(
196
200
  The list of merge requests.
197
201
  """
198
202
  path = f"{self.manager.path}/{self.encoded_id}/related_merge_requests"
199
- result = self.manager.gitlab.http_get(path, **kwargs)
203
+ result = self.manager.gitlab.http_list(path, **kwargs)
200
204
  if TYPE_CHECKING:
201
- assert isinstance(result, dict)
205
+ assert not isinstance(result, requests.Response)
202
206
  return result
203
207
 
204
208
  @cli.register_custom_action(cls_names="ProjectIssue")
205
209
  @exc.on_http_error(exc.GitlabGetError)
206
- def closed_by(self, **kwargs: Any) -> Dict[str, Any]:
210
+ def closed_by(
211
+ self, **kwargs: Any
212
+ ) -> Union[client.GitlabList, List[Dict[str, Any]]]:
207
213
  """List merge requests that will close the issue when merged.
208
214
 
209
215
  Args:
@@ -217,9 +223,9 @@ class ProjectIssue(
217
223
  The list of merge requests.
218
224
  """
219
225
  path = f"{self.manager.path}/{self.encoded_id}/closed_by"
220
- result = self.manager.gitlab.http_get(path, **kwargs)
226
+ result = self.manager.gitlab.http_list(path, **kwargs)
221
227
  if TYPE_CHECKING:
222
- assert isinstance(result, dict)
228
+ assert not isinstance(result, requests.Response)
223
229
  return result
224
230
 
225
231
 
@@ -0,0 +1,43 @@
1
+ from gitlab.base import RESTManager, RESTObject
2
+ from gitlab.mixins import (
3
+ CreateMixin,
4
+ DeleteMixin,
5
+ ListMixin,
6
+ ObjectDeleteMixin,
7
+ SaveMixin,
8
+ UpdateMethod,
9
+ UpdateMixin,
10
+ )
11
+ from gitlab.types import RequiredOptional
12
+
13
+ __all__ = [
14
+ "ProjectPackageProtectionRule",
15
+ "ProjectPackageProtectionRuleManager",
16
+ ]
17
+
18
+
19
+ class ProjectPackageProtectionRule(ObjectDeleteMixin, SaveMixin, RESTObject):
20
+ _repr_attr = "package_name_pattern"
21
+
22
+
23
+ class ProjectPackageProtectionRuleManager(
24
+ ListMixin, CreateMixin, DeleteMixin, UpdateMixin, RESTManager
25
+ ):
26
+ _path = "/projects/{project_id}/packages/protection/rules"
27
+ _obj_cls = ProjectPackageProtectionRule
28
+ _from_parent_attrs = {"project_id": "id"}
29
+ _create_attrs = RequiredOptional(
30
+ required=(
31
+ "package_name_pattern",
32
+ "package_type",
33
+ "minimum_access_level_for_push",
34
+ ),
35
+ )
36
+ _update_attrs = RequiredOptional(
37
+ optional=(
38
+ "package_name_pattern",
39
+ "package_type",
40
+ "minimum_access_level_for_push",
41
+ ),
42
+ )
43
+ _update_method = UpdateMethod.PATCH
@@ -139,6 +139,27 @@ class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManage
139
139
  ProjectPipeline, CreateMixin.create(self, data, path=path, **kwargs)
140
140
  )
141
141
 
142
+ def latest(self, ref: Optional[str] = None, lazy: bool = False) -> ProjectPipeline:
143
+ """Get the latest pipeline for the most recent commit
144
+ on a specific ref in a project
145
+
146
+ Args:
147
+ ref: The branch or tag to check for the latest pipeline.
148
+ Defaults to the default branch when not specified.
149
+ Returns:
150
+ A Pipeline instance
151
+ """
152
+ data = {}
153
+ if ref:
154
+ data = {"ref": ref}
155
+ if TYPE_CHECKING:
156
+ assert self._obj_cls is not None
157
+ assert self.path is not None
158
+ server_data = self.gitlab.http_get(self.path + "/latest", query_data=data)
159
+ if TYPE_CHECKING:
160
+ assert not isinstance(server_data, requests.Response)
161
+ return self._obj_cls(self, server_data, lazy=lazy)
162
+
142
163
 
143
164
  class ProjectPipelineJob(RESTObject):
144
165
  pass
@@ -43,6 +43,7 @@ from .badges import ProjectBadgeManager # noqa: F401
43
43
  from .boards import ProjectBoardManager # noqa: F401
44
44
  from .branches import ProjectBranchManager, ProjectProtectedBranchManager # noqa: F401
45
45
  from .ci_lint import ProjectCiLintManager # noqa: F401
46
+ from .cluster_agents import ProjectClusterAgentManager # noqa: F401
46
47
  from .clusters import ProjectClusterManager # noqa: F401
47
48
  from .commits import ProjectCommitManager # noqa: F401
48
49
  from .container_registry import ProjectRegistryRepositoryManager # noqa: F401
@@ -75,6 +76,7 @@ from .merge_trains import ProjectMergeTrainManager # noqa: F401
75
76
  from .milestones import ProjectMilestoneManager # noqa: F401
76
77
  from .notes import ProjectNoteManager # noqa: F401
77
78
  from .notification_settings import ProjectNotificationSettingsManager # noqa: F401
79
+ from .package_protection_rules import ProjectPackageProtectionRuleManager
78
80
  from .packages import GenericPackageManager, ProjectPackageManager # noqa: F401
79
81
  from .pages import ProjectPagesDomainManager # noqa: F401
80
82
  from .pipelines import ( # noqa: F401
@@ -84,6 +86,9 @@ from .pipelines import ( # noqa: F401
84
86
  )
85
87
  from .project_access_tokens import ProjectAccessTokenManager # noqa: F401
86
88
  from .push_rules import ProjectPushRulesManager # noqa: F401
89
+ from .registry_protection_rules import ( # noqa: F401
90
+ ProjectRegistryProtectionRuleManager,
91
+ )
87
92
  from .releases import ProjectReleaseManager # noqa: F401
88
93
  from .repositories import RepositoryMixin
89
94
  from .resource_groups import ProjectResourceGroupManager
@@ -179,6 +184,7 @@ class Project(
179
184
  branches: ProjectBranchManager
180
185
  ci_lint: ProjectCiLintManager
181
186
  clusters: ProjectClusterManager
187
+ cluster_agents: ProjectClusterAgentManager
182
188
  commits: ProjectCommitManager
183
189
  customattributes: ProjectCustomAttributeManager
184
190
  deployments: ProjectDeploymentManager
@@ -209,6 +215,7 @@ class Project(
209
215
  notes: ProjectNoteManager
210
216
  notificationsettings: ProjectNotificationSettingsManager
211
217
  packages: ProjectPackageManager
218
+ package_protection_rules: ProjectPackageProtectionRuleManager
212
219
  pagesdomains: ProjectPagesDomainManager
213
220
  pipelines: ProjectPipelineManager
214
221
  pipelineschedules: ProjectPipelineScheduleManager
@@ -216,6 +223,7 @@ class Project(
216
223
  protectedbranches: ProjectProtectedBranchManager
217
224
  protectedtags: ProjectProtectedTagManager
218
225
  pushrules: ProjectPushRulesManager
226
+ registry_protection_rules: ProjectRegistryProtectionRuleManager
219
227
  releases: ProjectReleaseManager
220
228
  resource_groups: ProjectResourceGroupManager
221
229
  remote_mirrors: "ProjectRemoteMirrorManager"
@@ -0,0 +1,35 @@
1
+ from gitlab.base import RESTManager, RESTObject
2
+ from gitlab.mixins import CreateMixin, ListMixin, SaveMixin, UpdateMethod, UpdateMixin
3
+ from gitlab.types import RequiredOptional
4
+
5
+ __all__ = [
6
+ "ProjectRegistryProtectionRule",
7
+ "ProjectRegistryProtectionRuleManager",
8
+ ]
9
+
10
+
11
+ class ProjectRegistryProtectionRule(SaveMixin, RESTObject):
12
+ _repr_attr = "repository_path_pattern"
13
+
14
+
15
+ class ProjectRegistryProtectionRuleManager(
16
+ ListMixin, CreateMixin, UpdateMixin, RESTManager
17
+ ):
18
+ _path = "/projects/{project_id}/registry/protection/rules"
19
+ _obj_cls = ProjectRegistryProtectionRule
20
+ _from_parent_attrs = {"project_id": "id"}
21
+ _create_attrs = RequiredOptional(
22
+ required=("repository_path_pattern",),
23
+ optional=(
24
+ "minimum_access_level_for_push",
25
+ "minimum_access_level_for_delete",
26
+ ),
27
+ )
28
+ _update_attrs = RequiredOptional(
29
+ optional=(
30
+ "repository_path_pattern",
31
+ "minimum_access_level_for_push",
32
+ "minimum_access_level_for_delete",
33
+ ),
34
+ )
35
+ _update_method = UpdateMethod.PATCH
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-gitlab
3
- Version: 4.6.0
3
+ Version: 4.8.0
4
4
  Summary: A python wrapper for the GitLab API
5
5
  Author-email: Gauvain Pocentek <gauvain@pocentek.net>
6
6
  Maintainer-email: John Villalovos <john@sodarock.com>, Max Wittig <max.wittig@siemens.com>, Nejc Habjan <nejc.habjan@siemens.com>, Roger Meier <r.meier@siemens.com>
@@ -1,22 +1,22 @@
1
1
  gitlab/__init__.py,sha256=bd8BSLyUUjtHMKtzmf-T5855W6FUHcuhIwx2hNu0w2o,1382
2
2
  gitlab/__main__.py,sha256=HTesNl0UAU6mPb9EXWkTKMy6Q6pAUxGi3iPnDHTE2uE,68
3
- gitlab/_version.py,sha256=JMTtJPASormHXzXDVC7oPZIMS8qox4-C3RSdjtoyR1E,249
3
+ gitlab/_version.py,sha256=3TRBcm3XV1lQcTc_QJPz-wxp5A9NuRqyRAdwFZNNgk0,249
4
4
  gitlab/base.py,sha256=5cotawlHD01Vw88aN4o7wNIhDyk_bmcwubX4mbOpnVo,13780
5
- gitlab/cli.py,sha256=PLcL8P0hhjtixHimdPRzY-hDKmLzeFyMB8YzJUIJNOw,13250
6
- gitlab/client.py,sha256=7HHsmoP4UZgznapb-0_M3Z7ZJx0NJzzoF8ChcwNDzdY,48774
5
+ gitlab/cli.py,sha256=d3-LtZuA1Fgon5wZWn4c3E70fTIu4mM4Juyhh3F8EBs,12416
6
+ gitlab/client.py,sha256=fPezDHNi4kJxzGxGeDWOWsKmKy76wVR4-fteCgDrY4I,49296
7
7
  gitlab/config.py,sha256=T1DgUXD0-MN2qNszrv-SO5d4uy0FITnNN0vWJgOt2yo,9088
8
8
  gitlab/const.py,sha256=rtPU-fxVSOvgpueoQVTvZGQp6iAZ-aa3nsY4RcSs_M4,5352
9
9
  gitlab/exceptions.py,sha256=Ruz9LusKMRu6SyV1vXpOu_UTCi3XU64A9BeHsqKASOw,8303
10
- gitlab/mixins.py,sha256=O2qb7qH7T_dHd6_c9QOaBAYNdzLgyffF7DIezndT8nQ,36591
10
+ gitlab/mixins.py,sha256=7iPlzqGmd5Ew2RLzRzRWsJ4r8Bn6wteUj791BJrjtXc,36645
11
11
  gitlab/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  gitlab/types.py,sha256=lepiiI_YOr94B4koqIHuY70tszZC_X3YW4lDvbadbI8,3312
13
- gitlab/utils.py,sha256=PNf4GT25MzgFIYa3e9_7FxleUYPqkt8DRPe4tYc0vUI,6396
13
+ gitlab/utils.py,sha256=9WwqvrpYkKZCXFlLZmbEiVvbGWv3ubmi05HiABhwGSA,6569
14
14
  gitlab/_backends/__init__.py,sha256=WalQZRIDzw19FuNxraG7fvck6ddg4cdNd3bi53QKvZM,392
15
15
  gitlab/_backends/protocol.py,sha256=m5qSz1o3i0H4XJCWnqx0wIFilOIU9cKxzFsYxLL6Big,842
16
16
  gitlab/_backends/requests_backend.py,sha256=CrSDTfkvi17dT4kTU8R3qQFBNCPJqEfBJq4gJ2GXleA,5534
17
17
  gitlab/v4/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- gitlab/v4/cli.py,sha256=buPexbNJeoi5rwnMV1q7G7zwcwof3m_Xs111yIUawq8,21942
19
- gitlab/v4/objects/__init__.py,sha256=F_ff0QyakRXOdUzAcIuJCdpLXFvPxW8ZTouYErBQjC4,1990
18
+ gitlab/v4/cli.py,sha256=vVWbeOWUdIPagCRcR-myY6E5tPVmhomroT1y6r2HwsQ,22721
19
+ gitlab/v4/objects/__init__.py,sha256=PKdDFYJ4JCPjcF7lJIwasdquvXj8TnqW0u729o-nG2w,2101
20
20
  gitlab/v4/objects/access_requests.py,sha256=pPYwB-b4fvKVdBoBRpzx2ensiMXbQwmBkN0XBh6ScaA,923
21
21
  gitlab/v4/objects/appearance.py,sha256=uMt0bjo_KlxwKHItFzGMY9vXm5e489UG_o0EcFVJE8E,1932
22
22
  gitlab/v4/objects/applications.py,sha256=FB1Se6Xa3CFLJlSeZ7lLoyAwY8cAF4DUDsuc59NGpcs,591
@@ -29,11 +29,12 @@ gitlab/v4/objects/branches.py,sha256=DgVwmi5CK9hW2ocGSdd-c71oDrq4WrOVEp0421S_iuY
29
29
  gitlab/v4/objects/broadcast_messages.py,sha256=EtjzOFwIsjqZe5jaPwYMdUa1QjNcWfoCl6-M363HJbI,1103
30
30
  gitlab/v4/objects/bulk_imports.py,sha256=rexiCZiZLRdzDlFqRjW4MN8JGfbaYV71s1BxKZx3JQs,1573
31
31
  gitlab/v4/objects/ci_lint.py,sha256=HhU-PJsQdymh3p0wFxB0ecf84Sd0UaiX0V3YEYQjMqg,2308
32
+ gitlab/v4/objects/cluster_agents.py,sha256=zN-CEZVomcGXQSh7mzJScd55LzQmVwbDcLKVgUKaLhU,817
32
33
  gitlab/v4/objects/clusters.py,sha256=Gsz5Jf2L27gzdbQvb5gKY20gxFT4jZASAofy5qL5hxQ,3750
33
- gitlab/v4/objects/commits.py,sha256=ya4hglN4IFVTpa7BLAjMf-xty5E6bLbpzp5vthQdzUk,8292
34
+ gitlab/v4/objects/commits.py,sha256=31O2_eU19Z7Df1KKqfXY61lFDARUcQJAqUZDWvqlBI0,8972
34
35
  gitlab/v4/objects/container_registry.py,sha256=w-t1nbjavJ18MEPmbIrEUcaOP2mXsr3Oc1Ub_1U95Y0,3360
35
36
  gitlab/v4/objects/custom_attributes.py,sha256=48HKZ2C65UUU65FdvKrefFzRMVKRbG-rl58OE9FAuyI,1875
36
- gitlab/v4/objects/deploy_keys.py,sha256=i05JPwOOKq_h5R4WV4RmY-r78ksVEliEWTnxgqgCz8c,1878
37
+ gitlab/v4/objects/deploy_keys.py,sha256=hv9WSzbE9TqRskVSfyNfATGxSw7-urId2tH6ag1hnq4,1947
37
38
  gitlab/v4/objects/deploy_tokens.py,sha256=SEVSCr9D7LBj7qoE_D1-wK4pr2daFsVwBbhpm4m1Ey0,2110
38
39
  gitlab/v4/objects/deployments.py,sha256=xN7FXcjpHxq25OD4ywhZkbq_5EqlU5dQp-DbWYe1NR0,2889
39
40
  gitlab/v4/objects/discussions.py,sha256=LK-nyJx0ncevYXehiI4gyUQCrY3feZ5mUK6HpqhCeDU,3457
@@ -43,14 +44,14 @@ gitlab/v4/objects/epics.py,sha256=HKLpEL7_K54M6prGjga3qw5VfI2ZGGxBbfz42Oumvr0,41
43
44
  gitlab/v4/objects/events.py,sha256=20yCSlR9XB75AwMzatmAt4VdT9PL2nX0t1p1sAWbrvI,7067
44
45
  gitlab/v4/objects/export_import.py,sha256=XVmrSq_qHwQr3XamFPfISEXnlBd-icJRm2CCa3V2puY,1909
45
46
  gitlab/v4/objects/features.py,sha256=N7T52I2JyNIgD1JejrSr8fNa14ZlAUxrS2VceUekhj0,1973
46
- gitlab/v4/objects/files.py,sha256=9-cGDO3EdNA5xJ-MmVl8oMSLNkzRbnHAEV68_CPBwOQ,10506
47
+ gitlab/v4/objects/files.py,sha256=9M7pocu_n9JDRAPu5BqIt398emYD5WVnGh3wAJinPX8,10608
47
48
  gitlab/v4/objects/geo_nodes.py,sha256=tD9piU7OIZgbNQRUeLTFPtAJ6PVL_SI6tR_zh6Tm2M8,3686
48
49
  gitlab/v4/objects/group_access_tokens.py,sha256=EijY0sfsp0Gtx_q4JLBeLL3jphx5b_6-nTzKxV272jc,1023
49
50
  gitlab/v4/objects/groups.py,sha256=YxOeaRYUjhu8PicCicVT7Eua04YuyOLAc8J13V7r9Gg,15958
50
51
  gitlab/v4/objects/hooks.py,sha256=1uDYi09GOmgR6t7gVT06CeMGL0ZZ1N5swz1KMtsybDk,3598
51
52
  gitlab/v4/objects/integrations.py,sha256=QWl5ZnE1oivt4ho9qJa_o268ORdaW35D4klBRy1jUyQ,9229
52
53
  gitlab/v4/objects/invitations.py,sha256=ya9x7xhL1oSbx-FLJud-lHKmbYQoTplZlAbjsZip2CI,2734
53
- gitlab/v4/objects/issues.py,sha256=FToUg3rm-nrcHPwwrx5SQCyNSNBOC7xNJmHa2S5szdI,10234
54
+ gitlab/v4/objects/issues.py,sha256=kxciXrLGxCsevJ2eoxpDdMLnw1kF4VrQTy4YB4AoN1U,10393
54
55
  gitlab/v4/objects/iterations.py,sha256=QsPOg0YB57ShQUdTQwa_wXtNfWAM2c53kWE6y5xjg6E,1561
55
56
  gitlab/v4/objects/job_token_scope.py,sha256=J69VVjtRgnTYQrFHFxOWQv3T2s6XRxb7uz57IAhnsdU,2622
56
57
  gitlab/v4/objects/jobs.py,sha256=g7l5dA6-99tyLDoohjJ_xZvGyMbeytn4L9T-h78NQaE,9140
@@ -65,13 +66,15 @@ gitlab/v4/objects/milestones.py,sha256=LHAGYJlTm2ed3eqv4mTO-QZ7vRbwGXRFpre_G4gHd
65
66
  gitlab/v4/objects/namespaces.py,sha256=5_km8RP_OLZoRm6u-p53S2AM5UsHyJ4j65fi5fGIoLo,1535
66
67
  gitlab/v4/objects/notes.py,sha256=Y0wrSD2pwvzX1RfyzyeXMMBy0_jOsWsaIUpa6CYWprg,8588
67
68
  gitlab/v4/objects/notification_settings.py,sha256=zhltGjuu1HiqdON2v9-uKu7Z6TOOBOgQ3GdWgfEAJ64,2061
69
+ gitlab/v4/objects/package_protection_rules.py,sha256=OVO9R_ePWjNFKK8LCuycGpCds6Yj6ZFtirZRSlzKa7Q,1129
68
70
  gitlab/v4/objects/packages.py,sha256=4tEocanjw1GlFvfOncCY0Z-jJfjiFLhZeUiBIjLz9_g,7225
69
71
  gitlab/v4/objects/pages.py,sha256=o6EHYJa-4qo8-IolppZk5Y5o64CAIlLceW2LPNR3nM4,1141
70
72
  gitlab/v4/objects/personal_access_tokens.py,sha256=vMsAytE5czai3fpyTCyV1sR3cZDZRhvI06u08L8O7mw,1315
71
- gitlab/v4/objects/pipelines.py,sha256=YfH-JN4_u1XhNmxHTFPGeuXe7Gi3aWhPiRtBwbvo7lg,9818
73
+ gitlab/v4/objects/pipelines.py,sha256=nQrzNW6WCTcDCqz_nl8i7YYGpXfRaEVGGpeRdObjeW0,10664
72
74
  gitlab/v4/objects/project_access_tokens.py,sha256=z_BCaBtXC7wzGVN6Ip0H72VwHID8XEBHDddCw0J8hO0,1043
73
- gitlab/v4/objects/projects.py,sha256=IS9QARQ36DU1B6S8XvmpxjqciWOygqSC9_wQRFDuoBg,44401
75
+ gitlab/v4/objects/projects.py,sha256=nak5EdNhUFSXznSddbMZZ3kBTeOyidOTMX7nFSO0MZg,44824
74
76
  gitlab/v4/objects/push_rules.py,sha256=0dKMWEzF5h1zATh0_j_SvjQ7HKx9_5M7J9hzDGB66Rc,3041
77
+ gitlab/v4/objects/registry_protection_rules.py,sha256=bZsUAjoxwQEHOfr4Bxz6FQA2MGHZpWOFz00o99hcfI4,1092
75
78
  gitlab/v4/objects/releases.py,sha256=j4_45oOj2yaA2XZ3fwBcKhFJ6li4vQy_zyr013LKfvY,1972
76
79
  gitlab/v4/objects/repositories.py,sha256=RfOzuEvq_dBY1t6KaHG939tfsto9PiyMi-y_ikXSqkU,11221
77
80
  gitlab/v4/objects/resource_groups.py,sha256=fYYnA2YO9CSTzxwImVCVPSiPkIeNpKRrPj7dpzwati4,1427
@@ -91,10 +94,10 @@ gitlab/v4/objects/triggers.py,sha256=UAERq_C-QdPBbBQPHLh5IfhpkdDeIxdnVGPHfu9Qy5Y
91
94
  gitlab/v4/objects/users.py,sha256=_gGrTwcE17jeoXIPgfFSv54jtF1_9C1R0Y0hhssTvXY,21381
92
95
  gitlab/v4/objects/variables.py,sha256=S0Vz32jEpUbo4J2js8gMPPTVpcy1ge5FYVHLiPz9c-A,2627
93
96
  gitlab/v4/objects/wikis.py,sha256=JtI1cQqZV1_PRfKVlQRMh4LZjdxEfi9T2VuFYv6PrV8,1775
94
- python_gitlab-4.6.0.dist-info/AUTHORS,sha256=Z0P61GJSVnp7iFbRcMezhx3f4zMyPkVmG--TWaRo768,526
95
- python_gitlab-4.6.0.dist-info/COPYING,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651
96
- python_gitlab-4.6.0.dist-info/METADATA,sha256=WUoK-pngHNFgI0nPSIetLjJw1vSaPwfspoZoWqDZfbM,8228
97
- python_gitlab-4.6.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
98
- python_gitlab-4.6.0.dist-info/entry_points.txt,sha256=nhpKLLP_uQPFByn8UtE9zsvQQwa402t52o_Cw9IFXMo,43
99
- python_gitlab-4.6.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
100
- python_gitlab-4.6.0.dist-info/RECORD,,
97
+ python_gitlab-4.8.0.dist-info/AUTHORS,sha256=Z0P61GJSVnp7iFbRcMezhx3f4zMyPkVmG--TWaRo768,526
98
+ python_gitlab-4.8.0.dist-info/COPYING,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651
99
+ python_gitlab-4.8.0.dist-info/METADATA,sha256=xyrDULh_rYlzaBL20eATjkp8ZugRAm0v772cwmShWFs,8228
100
+ python_gitlab-4.8.0.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
101
+ python_gitlab-4.8.0.dist-info/entry_points.txt,sha256=nhpKLLP_uQPFByn8UtE9zsvQQwa402t52o_Cw9IFXMo,43
102
+ python_gitlab-4.8.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
103
+ python_gitlab-4.8.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5