python-gitlab 5.6.0__py3-none-any.whl → 6.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. gitlab/__init__.py +0 -1
  2. gitlab/_backends/protocol.py +9 -13
  3. gitlab/_backends/requests_backend.py +12 -12
  4. gitlab/_version.py +1 -1
  5. gitlab/base.py +48 -48
  6. gitlab/cli.py +15 -25
  7. gitlab/client.py +114 -140
  8. gitlab/config.py +16 -17
  9. gitlab/const.py +2 -0
  10. gitlab/exceptions.py +7 -5
  11. gitlab/mixins.py +154 -238
  12. gitlab/types.py +13 -14
  13. gitlab/utils.py +32 -43
  14. gitlab/v4/cli.py +51 -54
  15. gitlab/v4/objects/__init__.py +1 -0
  16. gitlab/v4/objects/access_requests.py +11 -3
  17. gitlab/v4/objects/appearance.py +12 -14
  18. gitlab/v4/objects/applications.py +5 -6
  19. gitlab/v4/objects/artifacts.py +10 -17
  20. gitlab/v4/objects/audit_events.py +4 -19
  21. gitlab/v4/objects/award_emojis.py +13 -57
  22. gitlab/v4/objects/badges.py +4 -19
  23. gitlab/v4/objects/boards.py +7 -27
  24. gitlab/v4/objects/branches.py +26 -14
  25. gitlab/v4/objects/broadcast_messages.py +3 -13
  26. gitlab/v4/objects/bulk_imports.py +6 -14
  27. gitlab/v4/objects/ci_lint.py +7 -13
  28. gitlab/v4/objects/cluster_agents.py +3 -13
  29. gitlab/v4/objects/clusters.py +13 -23
  30. gitlab/v4/objects/commits.py +23 -28
  31. gitlab/v4/objects/container_registry.py +13 -19
  32. gitlab/v4/objects/custom_attributes.py +16 -21
  33. gitlab/v4/objects/deploy_keys.py +22 -19
  34. gitlab/v4/objects/deploy_tokens.py +14 -32
  35. gitlab/v4/objects/deployments.py +13 -15
  36. gitlab/v4/objects/discussions.py +13 -29
  37. gitlab/v4/objects/draft_notes.py +4 -14
  38. gitlab/v4/objects/environments.py +13 -21
  39. gitlab/v4/objects/epics.py +14 -17
  40. gitlab/v4/objects/events.py +27 -79
  41. gitlab/v4/objects/export_import.py +7 -19
  42. gitlab/v4/objects/features.py +11 -12
  43. gitlab/v4/objects/files.py +23 -38
  44. gitlab/v4/objects/geo_nodes.py +7 -11
  45. gitlab/v4/objects/group_access_tokens.py +6 -13
  46. gitlab/v4/objects/groups.py +42 -37
  47. gitlab/v4/objects/hooks.py +4 -17
  48. gitlab/v4/objects/integrations.py +7 -18
  49. gitlab/v4/objects/invitations.py +12 -23
  50. gitlab/v4/objects/issues.py +21 -27
  51. gitlab/v4/objects/iterations.py +4 -8
  52. gitlab/v4/objects/job_token_scope.py +18 -14
  53. gitlab/v4/objects/jobs.py +17 -32
  54. gitlab/v4/objects/keys.py +8 -11
  55. gitlab/v4/objects/labels.py +19 -30
  56. gitlab/v4/objects/ldap.py +25 -9
  57. gitlab/v4/objects/member_roles.py +102 -0
  58. gitlab/v4/objects/members.py +11 -29
  59. gitlab/v4/objects/merge_request_approvals.py +31 -44
  60. gitlab/v4/objects/merge_requests.py +30 -40
  61. gitlab/v4/objects/merge_trains.py +3 -6
  62. gitlab/v4/objects/milestones.py +23 -29
  63. gitlab/v4/objects/namespaces.py +4 -10
  64. gitlab/v4/objects/notes.py +26 -69
  65. gitlab/v4/objects/notification_settings.py +5 -14
  66. gitlab/v4/objects/package_protection_rules.py +8 -8
  67. gitlab/v4/objects/packages.py +22 -37
  68. gitlab/v4/objects/pages.py +8 -14
  69. gitlab/v4/objects/personal_access_tokens.py +7 -10
  70. gitlab/v4/objects/pipelines.py +38 -47
  71. gitlab/v4/objects/project_access_tokens.py +6 -13
  72. gitlab/v4/objects/projects.py +63 -77
  73. gitlab/v4/objects/push_rules.py +13 -15
  74. gitlab/v4/objects/registry_protection_repository_rules.py +6 -7
  75. gitlab/v4/objects/registry_protection_rules.py +7 -11
  76. gitlab/v4/objects/releases.py +6 -20
  77. gitlab/v4/objects/repositories.py +25 -34
  78. gitlab/v4/objects/resource_groups.py +10 -15
  79. gitlab/v4/objects/reviewers.py +4 -2
  80. gitlab/v4/objects/runners.py +14 -13
  81. gitlab/v4/objects/secure_files.py +8 -21
  82. gitlab/v4/objects/service_accounts.py +7 -5
  83. gitlab/v4/objects/settings.py +13 -14
  84. gitlab/v4/objects/sidekiq.py +17 -18
  85. gitlab/v4/objects/snippets.py +78 -66
  86. gitlab/v4/objects/statistics.py +8 -23
  87. gitlab/v4/objects/status_checks.py +6 -3
  88. gitlab/v4/objects/tags.py +4 -13
  89. gitlab/v4/objects/templates.py +11 -59
  90. gitlab/v4/objects/todos.py +3 -6
  91. gitlab/v4/objects/topics.py +10 -21
  92. gitlab/v4/objects/triggers.py +3 -13
  93. gitlab/v4/objects/users.py +101 -93
  94. gitlab/v4/objects/variables.py +4 -19
  95. gitlab/v4/objects/wikis.py +4 -19
  96. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info}/METADATA +3 -2
  97. python_gitlab-6.1.0.dist-info/RECORD +107 -0
  98. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info}/WHEEL +1 -1
  99. python_gitlab-5.6.0.dist-info/RECORD +0 -106
  100. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info}/entry_points.txt +0 -0
  101. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info/licenses}/AUTHORS +0 -0
  102. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info/licenses}/COPYING +0 -0
  103. {python_gitlab-5.6.0.dist-info → python_gitlab-6.1.0.dist-info}/top_level.txt +0 -0
gitlab/__init__.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
1
  #
3
2
  # Copyright (C) 2013-2019 Gauvain Pocentek, 2019-2023 python-gitlab team
4
3
  #
@@ -1,15 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
- import sys
3
- from typing import Any, Dict, Optional, Union
4
+ from typing import Any, Protocol
4
5
 
5
6
  import requests
6
7
  from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore
7
8
 
8
- if sys.version_info >= (3, 8):
9
- from typing import Protocol
10
- else:
11
- from typing_extensions import Protocol
12
-
13
9
 
14
10
  class BackendResponse(Protocol):
15
11
  @abc.abstractmethod
@@ -22,11 +18,11 @@ class Backend(Protocol):
22
18
  self,
23
19
  method: str,
24
20
  url: str,
25
- json: Optional[Union[Dict[str, Any], bytes]],
26
- data: Optional[Union[Dict[str, Any], MultipartEncoder]],
27
- params: Optional[Any],
28
- timeout: Optional[float],
29
- verify: Optional[Union[bool, str]],
30
- stream: Optional[bool],
21
+ json: dict[str, Any] | bytes | None,
22
+ data: dict[str, Any] | MultipartEncoder | None,
23
+ params: Any | None,
24
+ timeout: float | None,
25
+ verify: bool | str | None,
26
+ stream: bool | None,
31
27
  **kwargs: Any,
32
28
  ) -> BackendResponse: ...
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
- from typing import Any, BinaryIO, Dict, Optional, TYPE_CHECKING, Union
4
+ from typing import Any, BinaryIO, TYPE_CHECKING
5
5
 
6
6
  import requests
7
7
  from requests import PreparedRequest
@@ -44,8 +44,8 @@ class JobTokenAuth(TokenAuth, AuthBase):
44
44
  @dataclasses.dataclass
45
45
  class SendData:
46
46
  content_type: str
47
- data: Optional[Union[Dict[str, Any], MultipartEncoder]] = None
48
- json: Optional[Union[Dict[str, Any], bytes]] = None
47
+ data: dict[str, Any] | MultipartEncoder | None = None
48
+ json: dict[str, Any] | bytes | None = None
49
49
 
50
50
  def __post_init__(self) -> None:
51
51
  if self.json is not None and self.data is not None:
@@ -84,7 +84,7 @@ class RequestsResponse(protocol.BackendResponse):
84
84
 
85
85
 
86
86
  class RequestsBackend(protocol.Backend):
87
- def __init__(self, session: Optional[requests.Session] = None) -> None:
87
+ def __init__(self, session: requests.Session | None = None) -> None:
88
88
  self._client: requests.Session = session or requests.Session()
89
89
 
90
90
  @property
@@ -93,8 +93,8 @@ class RequestsBackend(protocol.Backend):
93
93
 
94
94
  @staticmethod
95
95
  def prepare_send_data(
96
- files: Optional[Dict[str, Any]] = None,
97
- post_data: Optional[Union[Dict[str, Any], bytes, BinaryIO]] = None,
96
+ files: dict[str, Any] | None = None,
97
+ post_data: dict[str, Any] | bytes | BinaryIO | None = None,
98
98
  raw: bool = False,
99
99
  ) -> SendData:
100
100
  if files:
@@ -130,12 +130,12 @@ class RequestsBackend(protocol.Backend):
130
130
  self,
131
131
  method: str,
132
132
  url: str,
133
- json: Optional[Union[Dict[str, Any], bytes]] = None,
134
- data: Optional[Union[Dict[str, Any], MultipartEncoder]] = None,
135
- params: Optional[Any] = None,
136
- timeout: Optional[float] = None,
137
- verify: Optional[Union[bool, str]] = True,
138
- stream: Optional[bool] = False,
133
+ json: dict[str, Any] | bytes | None = None,
134
+ data: dict[str, Any] | MultipartEncoder | None = None,
135
+ params: Any | None = None,
136
+ timeout: float | None = None,
137
+ verify: bool | str | None = True,
138
+ stream: bool | None = False,
139
139
  **kwargs: Any,
140
140
  ) -> RequestsResponse:
141
141
  """Make HTTP request
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__ = "5.6.0"
6
+ __version__ = "6.1.0"
gitlab/base.py CHANGED
@@ -1,10 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import copy
2
4
  import importlib
3
5
  import json
4
6
  import pprint
5
7
  import textwrap
8
+ from collections.abc import Iterable
6
9
  from types import ModuleType
7
- from typing import Any, Dict, Iterable, Optional, Type, TYPE_CHECKING, Union
10
+ from typing import Any, ClassVar, Generic, TYPE_CHECKING, TypeVar
8
11
 
9
12
  import gitlab
10
13
  from gitlab import types as g_types
@@ -12,11 +15,7 @@ from gitlab.exceptions import GitlabParsingError
12
15
 
13
16
  from .client import Gitlab, GitlabList
14
17
 
15
- __all__ = [
16
- "RESTObject",
17
- "RESTObjectList",
18
- "RESTManager",
19
- ]
18
+ __all__ = ["RESTObject", "RESTObjectList", "RESTManager"]
20
19
 
21
20
 
22
21
  _URL_ATTRIBUTE_ERROR = (
@@ -40,20 +39,20 @@ class RESTObject:
40
39
  object's ``__repr__()`` method.
41
40
  """
42
41
 
43
- _id_attr: Optional[str] = "id"
44
- _attrs: Dict[str, Any]
42
+ _id_attr: str | None = "id"
43
+ _attrs: dict[str, Any]
45
44
  _created_from_list: bool # Indicates if object was created from a list() action
46
45
  _module: ModuleType
47
- _parent_attrs: Dict[str, Any]
48
- _repr_attr: Optional[str] = None
49
- _updated_attrs: Dict[str, Any]
46
+ _parent_attrs: dict[str, Any]
47
+ _repr_attr: str | None = None
48
+ _updated_attrs: dict[str, Any]
50
49
  _lazy: bool
51
- manager: "RESTManager"
50
+ manager: RESTManager[Any]
52
51
 
53
52
  def __init__(
54
53
  self,
55
- manager: "RESTManager",
56
- attrs: Dict[str, Any],
54
+ manager: RESTManager[Any],
55
+ attrs: dict[str, Any],
57
56
  *,
58
57
  created_from_list: bool = False,
59
58
  lazy: bool = False,
@@ -77,13 +76,13 @@ class RESTObject:
77
76
  self.__dict__["_parent_attrs"] = self.manager.parent_attrs
78
77
  self._create_managers()
79
78
 
80
- def __getstate__(self) -> Dict[str, Any]:
79
+ def __getstate__(self) -> dict[str, Any]:
81
80
  state = self.__dict__.copy()
82
81
  module = state.pop("_module")
83
82
  state["_module_name"] = module.__name__
84
83
  return state
85
84
 
86
- def __setstate__(self, state: Dict[str, Any]) -> None:
85
+ def __setstate__(self, state: dict[str, Any]) -> None:
87
86
  module_name = state.pop("_module_name")
88
87
  self.__dict__.update(state)
89
88
  self.__dict__["_module"] = importlib.import_module(module_name)
@@ -136,7 +135,7 @@ class RESTObject:
136
135
  def __setattr__(self, name: str, value: Any) -> None:
137
136
  self.__dict__["_updated_attrs"][name] = value
138
137
 
139
- def asdict(self, *, with_parent_attrs: bool = False) -> Dict[str, Any]:
138
+ def asdict(self, *, with_parent_attrs: bool = False) -> dict[str, Any]:
140
139
  data = {}
141
140
  if with_parent_attrs:
142
141
  data.update(copy.deepcopy(self._parent_attrs))
@@ -145,7 +144,7 @@ class RESTObject:
145
144
  return data
146
145
 
147
146
  @property
148
- def attributes(self) -> Dict[str, Any]:
147
+ def attributes(self) -> dict[str, Any]:
149
148
  return self.asdict(with_parent_attrs=True)
150
149
 
151
150
  def to_json(self, *, with_parent_attrs: bool = False, **kwargs: Any) -> str:
@@ -220,11 +219,11 @@ class RESTObject:
220
219
  # Since we have our own __setattr__ method, we can't use setattr()
221
220
  self.__dict__[attr] = manager
222
221
 
223
- def _update_attrs(self, new_attrs: Dict[str, Any]) -> None:
222
+ def _update_attrs(self, new_attrs: dict[str, Any]) -> None:
224
223
  self.__dict__["_updated_attrs"] = {}
225
224
  self.__dict__["_attrs"] = new_attrs
226
225
 
227
- def get_id(self) -> Optional[Union[int, str]]:
226
+ def get_id(self) -> int | str | None:
228
227
  """Returns the id of the resource."""
229
228
  if self._id_attr is None or not hasattr(self, self._id_attr):
230
229
  return None
@@ -234,7 +233,7 @@ class RESTObject:
234
233
  return id_val
235
234
 
236
235
  @property
237
- def _repr_value(self) -> Optional[str]:
236
+ def _repr_value(self) -> str | None:
238
237
  """Safely returns the human-readable resource name if present."""
239
238
  if self._repr_attr is None or not hasattr(self, self._repr_attr):
240
239
  return None
@@ -244,7 +243,7 @@ class RESTObject:
244
243
  return repr_val
245
244
 
246
245
  @property
247
- def encoded_id(self) -> Optional[Union[int, str]]:
246
+ def encoded_id(self) -> int | str | None:
248
247
  """Ensure that the ID is url-encoded so that it can be safely used in a URL
249
248
  path"""
250
249
  obj_id = self.get_id()
@@ -253,7 +252,10 @@ class RESTObject:
253
252
  return obj_id
254
253
 
255
254
 
256
- class RESTObjectList:
255
+ TObjCls = TypeVar("TObjCls", bound=RESTObject)
256
+
257
+
258
+ class RESTObjectList(Generic[TObjCls]):
257
259
  """Generator object representing a list of RESTObject's.
258
260
 
259
261
  This generator uses the Gitlab pagination system to fetch new data when
@@ -269,7 +271,7 @@ class RESTObjectList:
269
271
  """
270
272
 
271
273
  def __init__(
272
- self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: GitlabList
274
+ self, manager: RESTManager[TObjCls], obj_cls: type[TObjCls], _list: GitlabList
273
275
  ) -> None:
274
276
  """Creates an objects list from a GitlabList.
275
277
 
@@ -285,16 +287,16 @@ class RESTObjectList:
285
287
  self._obj_cls = obj_cls
286
288
  self._list = _list
287
289
 
288
- def __iter__(self) -> "RESTObjectList":
290
+ def __iter__(self) -> RESTObjectList[TObjCls]:
289
291
  return self
290
292
 
291
293
  def __len__(self) -> int:
292
294
  return len(self._list)
293
295
 
294
- def __next__(self) -> RESTObject:
296
+ def __next__(self) -> TObjCls:
295
297
  return self.next()
296
298
 
297
- def next(self) -> RESTObject:
299
+ def next(self) -> TObjCls:
298
300
  data = self._list.next()
299
301
  return self._obj_cls(self.manager, data, created_from_list=True)
300
302
 
@@ -304,7 +306,7 @@ class RESTObjectList:
304
306
  return self._list.current_page
305
307
 
306
308
  @property
307
- def prev_page(self) -> Optional[int]:
309
+ def prev_page(self) -> int | None:
308
310
  """The previous page number.
309
311
 
310
312
  If None, the current page is the first.
@@ -312,7 +314,7 @@ class RESTObjectList:
312
314
  return self._list.prev_page
313
315
 
314
316
  @property
315
- def next_page(self) -> Optional[int]:
317
+ def next_page(self) -> int | None:
316
318
  """The next page number.
317
319
 
318
320
  If None, the current page is the last.
@@ -320,22 +322,22 @@ class RESTObjectList:
320
322
  return self._list.next_page
321
323
 
322
324
  @property
323
- def per_page(self) -> Optional[int]:
325
+ def per_page(self) -> int | None:
324
326
  """The number of items per page."""
325
327
  return self._list.per_page
326
328
 
327
329
  @property
328
- def total_pages(self) -> Optional[int]:
330
+ def total_pages(self) -> int | None:
329
331
  """The total number of pages."""
330
332
  return self._list.total_pages
331
333
 
332
334
  @property
333
- def total(self) -> Optional[int]:
335
+ def total(self) -> int | None:
334
336
  """The total number of items."""
335
337
  return self._list.total
336
338
 
337
339
 
338
- class RESTManager:
340
+ class RESTManager(Generic[TObjCls]):
339
341
  """Base class for CRUD operations on objects.
340
342
 
341
343
  Derived class must define ``_path`` and ``_obj_cls``.
@@ -346,17 +348,17 @@ class RESTManager:
346
348
 
347
349
  _create_attrs: g_types.RequiredOptional = g_types.RequiredOptional()
348
350
  _update_attrs: g_types.RequiredOptional = g_types.RequiredOptional()
349
- _path: Optional[str] = None
350
- _obj_cls: Optional[Type[RESTObject]] = None
351
- _from_parent_attrs: Dict[str, Any] = {}
352
- _types: Dict[str, Type[g_types.GitlabAttribute]] = {}
353
-
354
- _computed_path: Optional[str]
355
- _parent: Optional[RESTObject]
356
- _parent_attrs: Dict[str, Any]
351
+ _path: ClassVar[str]
352
+ _obj_cls: type[TObjCls]
353
+ _from_parent_attrs: dict[str, Any] = {}
354
+ _types: dict[str, type[g_types.GitlabAttribute]] = {}
355
+
356
+ _computed_path: str
357
+ _parent: RESTObject | None
358
+ _parent_attrs: dict[str, Any]
357
359
  gitlab: Gitlab
358
360
 
359
- def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None:
361
+ def __init__(self, gl: Gitlab, parent: RESTObject | None = None) -> None:
360
362
  """REST manager constructor.
361
363
 
362
364
  Args:
@@ -368,19 +370,17 @@ class RESTManager:
368
370
  self._computed_path = self._compute_path()
369
371
 
370
372
  @property
371
- def parent_attrs(self) -> Optional[Dict[str, Any]]:
373
+ def parent_attrs(self) -> dict[str, Any] | None:
372
374
  return self._parent_attrs
373
375
 
374
- def _compute_path(self, path: Optional[str] = None) -> Optional[str]:
376
+ def _compute_path(self, path: str | None = None) -> str:
375
377
  self._parent_attrs = {}
376
378
  if path is None:
377
379
  path = self._path
378
- if path is None:
379
- return None
380
380
  if self._parent is None or not self._from_parent_attrs:
381
381
  return path
382
382
 
383
- data: Dict[str, Optional[gitlab.utils.EncodedId]] = {}
383
+ data: dict[str, gitlab.utils.EncodedId | None] = {}
384
384
  for self_attr, parent_attr in self._from_parent_attrs.items():
385
385
  if not hasattr(self._parent, parent_attr):
386
386
  data[self_attr] = None
@@ -390,5 +390,5 @@ class RESTManager:
390
390
  return path.format(**data)
391
391
 
392
392
  @property
393
- def path(self) -> Optional[str]:
393
+ def path(self) -> str:
394
394
  return self._computed_path
gitlab/cli.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import argparse
2
4
  import dataclasses
3
5
  import functools
@@ -6,19 +8,7 @@ import pathlib
6
8
  import re
7
9
  import sys
8
10
  from types import ModuleType
9
- from typing import (
10
- Any,
11
- Callable,
12
- cast,
13
- Dict,
14
- NoReturn,
15
- Optional,
16
- Tuple,
17
- Type,
18
- TYPE_CHECKING,
19
- TypeVar,
20
- Union,
21
- )
11
+ from typing import Any, Callable, cast, NoReturn, TYPE_CHECKING, TypeVar
22
12
 
23
13
  from requests.structures import CaseInsensitiveDict
24
14
 
@@ -33,11 +23,11 @@ camel_lowerupper_regex = re.compile(r"([a-z\d])([A-Z])")
33
23
 
34
24
  @dataclasses.dataclass
35
25
  class CustomAction:
36
- required: Tuple[str, ...]
37
- optional: Tuple[str, ...]
26
+ required: tuple[str, ...]
27
+ optional: tuple[str, ...]
38
28
  in_object: bool
39
29
  requires_id: bool # if the `_id_attr` value should be a required argument
40
- help: Optional[str] # help text for the custom action
30
+ help: str | None # help text for the custom action
41
31
 
42
32
 
43
33
  # custom_actions = {
@@ -45,7 +35,7 @@ class CustomAction:
45
35
  # action: CustomAction,
46
36
  # },
47
37
  # }
48
- custom_actions: Dict[str, Dict[str, CustomAction]] = {}
38
+ custom_actions: dict[str, dict[str, CustomAction]] = {}
49
39
 
50
40
 
51
41
  # For an explanation of how these type-hints work see:
@@ -57,12 +47,12 @@ __F = TypeVar("__F", bound=Callable[..., Any])
57
47
 
58
48
  def register_custom_action(
59
49
  *,
60
- cls_names: Union[str, Tuple[str, ...]],
61
- required: Tuple[str, ...] = (),
62
- optional: Tuple[str, ...] = (),
63
- custom_action: Optional[str] = None,
50
+ cls_names: str | tuple[str, ...],
51
+ required: tuple[str, ...] = (),
52
+ optional: tuple[str, ...] = (),
53
+ custom_action: str | None = None,
64
54
  requires_id: bool = True, # if the `_id_attr` value should be a required argument
65
- help: Optional[str] = None, # help text for the action
55
+ help: str | None = None, # help text for the action
66
56
  ) -> Callable[[__F], __F]:
67
57
  def wrap(f: __F) -> __F:
68
58
  @functools.wraps(f)
@@ -98,7 +88,7 @@ def register_custom_action(
98
88
  return wrap
99
89
 
100
90
 
101
- def die(msg: str, e: Optional[Exception] = None) -> NoReturn:
91
+ def die(msg: str, e: Exception | None = None) -> NoReturn:
102
92
  if e:
103
93
  msg = f"{msg} ({e})"
104
94
  sys.stderr.write(f"{msg}\n")
@@ -107,7 +97,7 @@ def die(msg: str, e: Optional[Exception] = None) -> NoReturn:
107
97
 
108
98
  def gitlab_resource_to_cls(
109
99
  gitlab_resource: str, namespace: ModuleType
110
- ) -> Type[RESTObject]:
100
+ ) -> type[RESTObject]:
111
101
  classes = CaseInsensitiveDict(namespace.__dict__)
112
102
  lowercase_class = gitlab_resource.replace("-", "")
113
103
  class_type = classes[lowercase_class]
@@ -117,7 +107,7 @@ def gitlab_resource_to_cls(
117
107
  return class_type
118
108
 
119
109
 
120
- def cls_to_gitlab_resource(cls: RESTObject) -> str:
110
+ def cls_to_gitlab_resource(cls: type[RESTObject]) -> str:
121
111
  dasherized_uppercase = camel_upperlower_regex.sub(r"\1-\2", cls.__name__)
122
112
  dasherized_lowercase = camel_lowerupper_regex.sub(r"\1-\2", dasherized_uppercase)
123
113
  return dasherized_lowercase.lower()