Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 76bc2b2

Browse files
feat(api): Make RESTManager generic on RESTObject class
Currently mixins like ListMixin are type hinted to return base RESTObject instead of a specific class like `MergeRequest`. The GetMixin and GetWithoutIdMixin solve this problem by defining a new `get` method for every defined class. However, this creates a lot of duplicated code. Make RESTManager use `typing.Generic` as its base class and use and assign the declared TypeVar to the `_obj_cls` attribute as a type of the passed class. Make both `_obj_cls` and `_path` attributes an abstract properties so that type checkers can check that those attributes were properly defined in subclasses. Mypy will only check then the class is instantiated which makes non-final subclasses possible. Unfortunately pylint will check the declarations not instantiations so add `# pylint: disable=abstract-method` comments to all non-final subclasses like ListMixin. Make `_path` attribute always be `str` instead of sometimes `None`. This eliminates unnecessary type checks. Change all mixins like ListMixin or GetMixin to a subclass. This makes the attribute declarations much cleaner as for example `_list_filters` is now the only attribute defined by ListMixin. Change SidekiqManager to not inherit from RESTManager and only copy its `__init__` method. This is because SidekiqManager never was a real manager and does not define `_path` or `_obj_cls`. Delete `tests/unit/meta/test_ensure_type_hints.py` file as the `get` method is no required to be defined for every class. Signed-off-by: Igor Ponomarev <[email protected]>
1 parent 671e711 commit 76bc2b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+758
-1461
lines changed

gitlab/base.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,19 @@
33
import json
44
import pprint
55
import textwrap
6+
from abc import ABCMeta, abstractmethod
67
from types import ModuleType
7-
from typing import Any, Dict, Iterable, Optional, Type, TYPE_CHECKING, Union
8+
from typing import (
9+
Any,
10+
Dict,
11+
Generic,
12+
Iterable,
13+
Optional,
14+
Type,
15+
TYPE_CHECKING,
16+
TypeVar,
17+
Union,
18+
)
819

920
import gitlab
1021
from gitlab import types as g_types
@@ -48,11 +59,11 @@ class RESTObject:
4859
_repr_attr: Optional[str] = None
4960
_updated_attrs: Dict[str, Any]
5061
_lazy: bool
51-
manager: "RESTManager"
62+
manager: "RESTManager[Any]"
5263

5364
def __init__(
5465
self,
55-
manager: "RESTManager",
66+
manager: "RESTManager[Any]",
5667
attrs: Dict[str, Any],
5768
*,
5869
created_from_list: bool = False,
@@ -269,7 +280,7 @@ class RESTObjectList:
269280
"""
270281

271282
def __init__(
272-
self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: GitlabList
283+
self, manager: "RESTManager[Any]", obj_cls: Type[RESTObject], _list: GitlabList
273284
) -> None:
274285
"""Creates an objects list from a GitlabList.
275286
@@ -335,7 +346,10 @@ def total(self) -> Optional[int]:
335346
return self._list.total
336347

337348

338-
class RESTManager:
349+
TObjCls = TypeVar("TObjCls", bound=RESTObject)
350+
351+
352+
class RESTManager(Generic[TObjCls], metaclass=ABCMeta):
339353
"""Base class for CRUD operations on objects.
340354
341355
Derived class must define ``_path`` and ``_obj_cls``.
@@ -346,16 +360,22 @@ class RESTManager:
346360

347361
_create_attrs: g_types.RequiredOptional = g_types.RequiredOptional()
348362
_update_attrs: g_types.RequiredOptional = g_types.RequiredOptional()
349-
_path: Optional[str] = None
350-
_obj_cls: Optional[Type[RESTObject]] = None
351363
_from_parent_attrs: Dict[str, Any] = {}
352364
_types: Dict[str, Type[g_types.GitlabAttribute]] = {}
353365

354-
_computed_path: Optional[str]
366+
_computed_path: str
355367
_parent: Optional[RESTObject]
356368
_parent_attrs: Dict[str, Any]
357369
gitlab: Gitlab
358370

371+
@property
372+
@abstractmethod
373+
def _path(self) -> str: ...
374+
375+
@property
376+
@abstractmethod
377+
def _obj_cls(self) -> type[TObjCls]: ...
378+
359379
def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None:
360380
"""REST manager constructor.
361381
@@ -371,12 +391,10 @@ def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None:
371391
def parent_attrs(self) -> Optional[Dict[str, Any]]:
372392
return self._parent_attrs
373393

374-
def _compute_path(self, path: Optional[str] = None) -> Optional[str]:
394+
def _compute_path(self, path: Optional[str] = None) -> str:
375395
self._parent_attrs = {}
376396
if path is None:
377397
path = self._path
378-
if path is None:
379-
return None
380398
if self._parent is None or not self._from_parent_attrs:
381399
return path
382400

@@ -390,5 +408,5 @@ def _compute_path(self, path: Optional[str] = None) -> Optional[str]:
390408
return path.format(**data)
391409

392410
@property
393-
def path(self) -> Optional[str]:
411+
def path(self) -> str:
394412
return self._computed_path

gitlab/client.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,11 @@ def auth(self) -> None:
397397
The `user` attribute will hold a `gitlab.objects.CurrentUser` object on
398398
success.
399399
"""
400-
self.user = self._objects.CurrentUserManager(self).get()
400+
user = self._objects.CurrentUserManager(self).get()
401+
self.user = user
401402

402-
if hasattr(self.user, "web_url") and hasattr(self.user, "username"):
403-
self._check_url(self.user.web_url, path=self.user.username)
403+
if hasattr(user, "web_url") and hasattr(user, "username"):
404+
self._check_url(user.web_url, path=user.username)
404405

405406
def version(self) -> Tuple[str, str]:
406407
"""Returns the version and revision of the gitlab server.

0 commit comments

Comments
 (0)