Skip to content

Commit 2673af0

Browse files
chore: add type-hints to gitlab/v4/cli.py
* Add type-hints to gitlab/v4/cli.py * Add required type-hints to other files based on adding type-hints to gitlab/v4/cli.py
1 parent fbbc0d4 commit 2673af0

File tree

2 files changed

+121
-40
lines changed

2 files changed

+121
-40
lines changed

.mypy.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[mypy]
2-
files = gitlab/*.py
2+
files = gitlab/*.py,gitlab/v4/cli.py
33

44
# disallow_incomplete_defs: This flag reports an error whenever it encounters a
55
# partly annotated function definition.

gitlab/v4/cli.py

+120-39
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
# You should have received a copy of the GNU Lesser General Public License
1717
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818

19+
import argparse
1920
import operator
2021
import sys
22+
from typing import Any, Dict, List, Optional, Type, TYPE_CHECKING, Union
2123

2224
import gitlab
2325
import gitlab.base
@@ -26,18 +28,31 @@
2628

2729

2830
class GitlabCLI(object):
29-
def __init__(self, gl, what, action, args):
30-
self.cls = cli.what_to_cls(what, namespace=gitlab.v4.objects)
31+
def __init__(
32+
self, gl: gitlab.Gitlab, what: str, action: str, args: Dict[str, str]
33+
) -> None:
34+
self.cls: Type[gitlab.base.RESTObject] = cli.what_to_cls(
35+
what, namespace=gitlab.v4.objects
36+
)
3137
self.cls_name = self.cls.__name__
3238
self.what = what.replace("-", "_")
3339
self.action = action.lower()
3440
self.gl = gl
3541
self.args = args
36-
self.mgr_cls = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager")
42+
self.mgr_cls: Union[
43+
Type[gitlab.mixins.CreateMixin],
44+
Type[gitlab.mixins.DeleteMixin],
45+
Type[gitlab.mixins.GetMixin],
46+
Type[gitlab.mixins.GetWithoutIdMixin],
47+
Type[gitlab.mixins.ListMixin],
48+
Type[gitlab.mixins.UpdateMixin],
49+
] = getattr(gitlab.v4.objects, self.cls.__name__ + "Manager")
3750
# We could do something smart, like splitting the manager name to find
3851
# parents, build the chain of managers to get to the final object.
3952
# Instead we do something ugly and efficient: interpolate variables in
4053
# the class _path attribute, and replace the value with the result.
54+
if TYPE_CHECKING:
55+
assert self.mgr_cls._path is not None
4156
self.mgr_cls._path = self.mgr_cls._path % self.args
4257
self.mgr = self.mgr_cls(gl)
4358

@@ -48,7 +63,7 @@ def __init__(self, gl, what, action, args):
4863
obj.set_from_cli(self.args[attr_name])
4964
self.args[attr_name] = obj.get()
5065

51-
def __call__(self):
66+
def __call__(self) -> Any:
5267
# Check for a method that matches object + action
5368
method = "do_%s_%s" % (self.what, self.action)
5469
if hasattr(self, method):
@@ -62,7 +77,7 @@ def __call__(self):
6277
# Finally try to find custom methods
6378
return self.do_custom()
6479

65-
def do_custom(self):
80+
def do_custom(self) -> Any:
6681
in_obj = cli.custom_actions[self.cls_name][self.action][2]
6782

6883
# Get the object (lazy), then act
@@ -72,14 +87,16 @@ def do_custom(self):
7287
for k in self.mgr._from_parent_attrs:
7388
data[k] = self.args[k]
7489
if not issubclass(self.cls, gitlab.mixins.GetWithoutIdMixin):
90+
if TYPE_CHECKING:
91+
assert isinstance(self.cls._id_attr, str)
7592
data[self.cls._id_attr] = self.args.pop(self.cls._id_attr)
76-
o = self.cls(self.mgr, data)
93+
obj = self.cls(self.mgr, data)
7794
method_name = self.action.replace("-", "_")
78-
return getattr(o, method_name)(**self.args)
95+
return getattr(obj, method_name)(**self.args)
7996
else:
8097
return getattr(self.mgr, self.action)(**self.args)
8198

82-
def do_project_export_download(self):
99+
def do_project_export_download(self) -> None:
83100
try:
84101
project = self.gl.projects.get(int(self.args["project_id"]), lazy=True)
85102
data = project.exports.get().download()
@@ -88,46 +105,75 @@ def do_project_export_download(self):
88105
except Exception as e:
89106
cli.die("Impossible to download the export", e)
90107

91-
def do_create(self):
108+
def do_create(self) -> gitlab.base.RESTObject:
109+
if TYPE_CHECKING:
110+
assert isinstance(self.mgr, gitlab.mixins.CreateMixin)
92111
try:
93-
return self.mgr.create(self.args)
112+
result = self.mgr.create(self.args)
94113
except Exception as e:
95114
cli.die("Impossible to create object", e)
115+
return result
96116

97-
def do_list(self):
117+
def do_list(
118+
self,
119+
) -> Union[gitlab.base.RESTObjectList, List[gitlab.base.RESTObject]]:
120+
if TYPE_CHECKING:
121+
assert isinstance(self.mgr, gitlab.mixins.ListMixin)
98122
try:
99-
return self.mgr.list(**self.args)
123+
result = self.mgr.list(**self.args)
100124
except Exception as e:
101125
cli.die("Impossible to list objects", e)
126+
return result
102127

103-
def do_get(self):
104-
id = None
105-
if not issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin):
106-
id = self.args.pop(self.cls._id_attr)
128+
def do_get(self) -> Optional[gitlab.base.RESTObject]:
129+
if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin):
130+
try:
131+
result = self.mgr.get(id=None, **self.args)
132+
except Exception as e:
133+
cli.die("Impossible to get object", e)
134+
return result
107135

136+
if TYPE_CHECKING:
137+
assert isinstance(self.mgr, gitlab.mixins.GetMixin)
138+
assert isinstance(self.cls._id_attr, str)
139+
140+
id = self.args.pop(self.cls._id_attr)
108141
try:
109-
return self.mgr.get(id, **self.args)
142+
result = self.mgr.get(id, lazy=False, **self.args)
110143
except Exception as e:
111144
cli.die("Impossible to get object", e)
145+
return result
112146

113-
def do_delete(self):
147+
def do_delete(self) -> None:
148+
if TYPE_CHECKING:
149+
assert isinstance(self.mgr, gitlab.mixins.DeleteMixin)
150+
assert isinstance(self.cls._id_attr, str)
114151
id = self.args.pop(self.cls._id_attr)
115152
try:
116153
self.mgr.delete(id, **self.args)
117154
except Exception as e:
118155
cli.die("Impossible to destroy object", e)
119156

120-
def do_update(self):
121-
id = None
122-
if not issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin):
157+
def do_update(self) -> Dict[str, Any]:
158+
if TYPE_CHECKING:
159+
assert isinstance(self.mgr, gitlab.mixins.UpdateMixin)
160+
if issubclass(self.mgr_cls, gitlab.mixins.GetWithoutIdMixin):
161+
id = None
162+
else:
163+
if TYPE_CHECKING:
164+
assert isinstance(self.cls._id_attr, str)
123165
id = self.args.pop(self.cls._id_attr)
166+
124167
try:
125-
return self.mgr.update(id, self.args)
168+
result = self.mgr.update(id, self.args)
126169
except Exception as e:
127170
cli.die("Impossible to update object", e)
171+
return result
128172

129173

130-
def _populate_sub_parser_by_class(cls, sub_parser):
174+
def _populate_sub_parser_by_class(
175+
cls: Type[gitlab.base.RESTObject], sub_parser: argparse._SubParsersAction
176+
) -> None:
131177
mgr_cls_name = cls.__name__ + "Manager"
132178
mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name)
133179

@@ -258,7 +304,7 @@ def _populate_sub_parser_by_class(cls, sub_parser):
258304
]
259305

260306

261-
def extend_parser(parser):
307+
def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
262308
subparsers = parser.add_subparsers(
263309
title="object", dest="what", help="Object to manipulate."
264310
)
@@ -287,7 +333,9 @@ def extend_parser(parser):
287333
return parser
288334

289335

290-
def get_dict(obj, fields):
336+
def get_dict(
337+
obj: Union[str, gitlab.base.RESTObject], fields: List[str]
338+
) -> Union[str, Dict[str, Any]]:
291339
if isinstance(obj, str):
292340
return obj
293341

@@ -297,19 +345,24 @@ def get_dict(obj, fields):
297345

298346

299347
class JSONPrinter(object):
300-
def display(self, d, **kwargs):
348+
def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None:
301349
import json # noqa
302350

303351
print(json.dumps(d))
304352

305-
def display_list(self, data, fields, **kwargs):
353+
def display_list(
354+
self,
355+
data: List[Union[str, gitlab.base.RESTObject]],
356+
fields: List[str],
357+
**kwargs: Any
358+
) -> None:
306359
import json # noqa
307360

308361
print(json.dumps([get_dict(obj, fields) for obj in data]))
309362

310363

311364
class YAMLPrinter(object):
312-
def display(self, d, **kwargs):
365+
def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None:
313366
try:
314367
import yaml # noqa
315368

@@ -321,7 +374,12 @@ def display(self, d, **kwargs):
321374
"to use the yaml output feature"
322375
)
323376

324-
def display_list(self, data, fields, **kwargs):
377+
def display_list(
378+
self,
379+
data: List[Union[str, gitlab.base.RESTObject]],
380+
fields: List[str],
381+
**kwargs: Any
382+
) -> None:
325383
try:
326384
import yaml # noqa
327385

@@ -339,12 +397,14 @@ def display_list(self, data, fields, **kwargs):
339397

340398

341399
class LegacyPrinter(object):
342-
def display(self, d, **kwargs):
400+
def display(self, d: Union[str, Dict[str, Any]], **kwargs: Any) -> None:
343401
verbose = kwargs.get("verbose", False)
344402
padding = kwargs.get("padding", 0)
345-
obj = kwargs.get("obj")
403+
obj: Optional[Union[Dict[str, Any], gitlab.base.RESTObject]] = kwargs.get("obj")
404+
if TYPE_CHECKING:
405+
assert obj is not None
346406

347-
def display_dict(d, padding):
407+
def display_dict(d: Dict[str, Any], padding: int) -> None:
348408
for k in sorted(d.keys()):
349409
v = d[k]
350410
if isinstance(v, dict):
@@ -369,6 +429,8 @@ def display_dict(d, padding):
369429
display_dict(attrs, padding)
370430

371431
else:
432+
if TYPE_CHECKING:
433+
assert isinstance(obj, gitlab.base.RESTObject)
372434
if obj._id_attr:
373435
id = getattr(obj, obj._id_attr)
374436
print("%s: %s" % (obj._id_attr.replace("_", "-"), id))
@@ -383,7 +445,12 @@ def display_dict(d, padding):
383445
line = line[:76] + "..."
384446
print(line)
385447

386-
def display_list(self, data, fields, **kwargs):
448+
def display_list(
449+
self,
450+
data: List[Union[str, gitlab.base.RESTObject]],
451+
fields: List[str],
452+
**kwargs: Any
453+
) -> None:
387454
verbose = kwargs.get("verbose", False)
388455
for obj in data:
389456
if isinstance(obj, gitlab.base.RESTObject):
@@ -393,14 +460,28 @@ def display_list(self, data, fields, **kwargs):
393460
print("")
394461

395462

396-
PRINTERS = {"json": JSONPrinter, "legacy": LegacyPrinter, "yaml": YAMLPrinter}
397-
398-
399-
def run(gl, what, action, args, verbose, output, fields):
400-
g_cli = GitlabCLI(gl, what, action, args)
463+
PRINTERS: Dict[
464+
str, Union[Type[JSONPrinter], Type[LegacyPrinter], Type[YAMLPrinter]]
465+
] = {
466+
"json": JSONPrinter,
467+
"legacy": LegacyPrinter,
468+
"yaml": YAMLPrinter,
469+
}
470+
471+
472+
def run(
473+
gl: gitlab.Gitlab,
474+
what: str,
475+
action: str,
476+
args: Dict[str, Any],
477+
verbose: bool,
478+
output: str,
479+
fields: List[str],
480+
) -> None:
481+
g_cli = GitlabCLI(gl=gl, what=what, action=action, args=args)
401482
data = g_cli()
402483

403-
printer = PRINTERS[output]()
484+
printer: Union[JSONPrinter, LegacyPrinter, YAMLPrinter] = PRINTERS[output]()
404485

405486
if isinstance(data, dict):
406487
printer.display(data, verbose=True, obj=data)

0 commit comments

Comments
 (0)