Skip to content

Commit 91c4f18

Browse files
igorp-collaboranejch
authored andcommitted
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 <igor.ponomarev@collabora.com>
1 parent 46dfc50 commit 91c4f18

Some content is hidden

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

88 files changed

+748
-536
lines changed

gitlab/base.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@
44
import pprint
55
import textwrap
66
from types import ModuleType
7-
from typing import Any, Dict, Iterable, Optional, Type, TYPE_CHECKING, Union
7+
from typing import (
8+
Any,
9+
ClassVar,
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]):
339353
"""Base class for CRUD operations on objects.
340354
341355
Derived class must define ``_path`` and ``_obj_cls``.
@@ -346,12 +360,12 @@ 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
363+
_path: ClassVar[str]
364+
_obj_cls: type[TObjCls]
351365
_from_parent_attrs: Dict[str, Any] = {}
352366
_types: Dict[str, Type[g_types.GitlabAttribute]] = {}
353367

354-
_computed_path: Optional[str]
368+
_computed_path: str
355369
_parent: Optional[RESTObject]
356370
_parent_attrs: Dict[str, Any]
357371
gitlab: Gitlab
@@ -371,12 +385,10 @@ def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None:
371385
def parent_attrs(self) -> Optional[Dict[str, Any]]:
372386
return self._parent_attrs
373387

374-
def _compute_path(self, path: Optional[str] = None) -> Optional[str]:
388+
def _compute_path(self, path: Optional[str] = None) -> str:
375389
self._parent_attrs = {}
376390
if path is None:
377391
path = self._path
378-
if path is None:
379-
return None
380392
if self._parent is None or not self._from_parent_attrs:
381393
return path
382394

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

392404
@property
393-
def path(self) -> Optional[str]:
405+
def path(self) -> str:
394406
return self._computed_path

gitlab/client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,10 @@ def auth(self) -> None:
397397
The `user` attribute will hold a `gitlab.objects.CurrentUser` object on
398398
success.
399399
"""
400-
# pylint: disable=line-too-long
401-
self.user = self._objects.CurrentUserManager(self).get() # type: ignore[assignment]
400+
self.user = self._objects.CurrentUserManager(self).get()
402401

403402
if hasattr(self.user, "web_url") and hasattr(self.user, "username"):
404-
self._check_url(self.user.web_url, path=self.user.username) # type: ignore[union-attr]
403+
self._check_url(self.user.web_url, path=self.user.username)
405404

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy