diff --git a/gitlab/base.py b/gitlab/base.py index f7ffaae66..9a8c80acf 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -4,7 +4,18 @@ import pprint import textwrap from types import ModuleType -from typing import Any, Dict, Iterable, Optional, Type, TYPE_CHECKING, Union +from typing import ( + Any, + ClassVar, + Dict, + Generic, + Iterable, + Optional, + Type, + TYPE_CHECKING, + TypeVar, + Union, +) import gitlab from gitlab import types as g_types @@ -48,11 +59,11 @@ class RESTObject: _repr_attr: Optional[str] = None _updated_attrs: Dict[str, Any] _lazy: bool - manager: "RESTManager" + manager: "RESTManager[Any]" def __init__( self, - manager: "RESTManager", + manager: "RESTManager[Any]", attrs: Dict[str, Any], *, created_from_list: bool = False, @@ -269,7 +280,7 @@ class RESTObjectList: """ def __init__( - self, manager: "RESTManager", obj_cls: Type[RESTObject], _list: GitlabList + self, manager: "RESTManager[Any]", obj_cls: Type[RESTObject], _list: GitlabList ) -> None: """Creates an objects list from a GitlabList. @@ -335,7 +346,10 @@ def total(self) -> Optional[int]: return self._list.total -class RESTManager: +TObjCls = TypeVar("TObjCls", bound=RESTObject) + + +class RESTManager(Generic[TObjCls]): """Base class for CRUD operations on objects. Derived class must define ``_path`` and ``_obj_cls``. @@ -346,12 +360,12 @@ class RESTManager: _create_attrs: g_types.RequiredOptional = g_types.RequiredOptional() _update_attrs: g_types.RequiredOptional = g_types.RequiredOptional() - _path: Optional[str] = None - _obj_cls: Optional[Type[RESTObject]] = None + _path: ClassVar[str] + _obj_cls: type[TObjCls] _from_parent_attrs: Dict[str, Any] = {} _types: Dict[str, Type[g_types.GitlabAttribute]] = {} - _computed_path: Optional[str] + _computed_path: str _parent: Optional[RESTObject] _parent_attrs: Dict[str, Any] gitlab: Gitlab @@ -371,12 +385,10 @@ def __init__(self, gl: Gitlab, parent: Optional[RESTObject] = None) -> None: def parent_attrs(self) -> Optional[Dict[str, Any]]: return self._parent_attrs - def _compute_path(self, path: Optional[str] = None) -> Optional[str]: + def _compute_path(self, path: Optional[str] = None) -> str: self._parent_attrs = {} if path is None: path = self._path - if path is None: - return None if self._parent is None or not self._from_parent_attrs: return path @@ -390,5 +402,5 @@ def _compute_path(self, path: Optional[str] = None) -> Optional[str]: return path.format(**data) @property - def path(self) -> Optional[str]: + def path(self) -> str: return self._computed_path diff --git a/gitlab/client.py b/gitlab/client.py index 45540ad22..87b324c34 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -397,11 +397,10 @@ def auth(self) -> None: The `user` attribute will hold a `gitlab.objects.CurrentUser` object on success. """ - # pylint: disable=line-too-long - self.user = self._objects.CurrentUserManager(self).get() # type: ignore[assignment] + self.user = self._objects.CurrentUserManager(self).get() if hasattr(self.user, "web_url") and hasattr(self.user, "username"): - self._check_url(self.user.web_url, path=self.user.username) # type: ignore[union-attr] + self._check_url(self.user.web_url, path=self.user.username) def version(self) -> Tuple[str, str]: """Returns the version and revision of the gitlab server. diff --git a/gitlab/mixins.py b/gitlab/mixins.py index e738a5c0b..df4caa40b 100644 --- a/gitlab/mixins.py +++ b/gitlab/mixins.py @@ -10,7 +10,6 @@ Optional, overload, Tuple, - Type, TYPE_CHECKING, Union, ) @@ -48,14 +47,12 @@ if TYPE_CHECKING: # When running mypy we use these as the base classes - _RestManagerBase = base.RESTManager _RestObjectBase = base.RESTObject else: - _RestManagerBase = object _RestObjectBase = object -class HeadMixin(_RestManagerBase): +class HeadMixin(base.RESTManager[base.TObjCls]): @exc.on_http_error(exc.GitlabHeadError) def head( self, id: Optional[Union[str, int]] = None, **kwargs: Any @@ -73,9 +70,6 @@ def head( GitlabAuthenticationError: If authentication is not correct GitlabHeadError: If the server cannot perform the request """ - if TYPE_CHECKING: - assert self.path is not None - path = self.path if id is not None: path = f"{path}/{utils.EncodedId(id)}" @@ -83,20 +77,13 @@ def head( return self.gitlab.http_head(path, **kwargs) -class GetMixin(HeadMixin, _RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] +class GetMixin(HeadMixin[base.TObjCls]): _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab @exc.on_http_error(exc.GitlabGetError) def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any - ) -> base.RESTObject: + ) -> base.TObjCls: """Retrieve a single object. Args: @@ -116,8 +103,6 @@ def get( if isinstance(id, str): id = utils.EncodedId(id) path = f"{self.path}/{id}" - if TYPE_CHECKING: - assert self._obj_cls is not None if lazy is True: if TYPE_CHECKING: assert self._obj_cls._id_attr is not None @@ -128,18 +113,11 @@ def get( return self._obj_cls(self, server_data, lazy=lazy) -class GetWithoutIdMixin(HeadMixin, _RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] +class GetWithoutIdMixin(HeadMixin[base.TObjCls]): _optional_get_attrs: Tuple[str, ...] = () - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab @exc.on_http_error(exc.GitlabGetError) - def get(self, **kwargs: Any) -> base.RESTObject: + def get(self, **kwargs: Any) -> base.TObjCls: """Retrieve a single object. Args: @@ -152,12 +130,9 @@ def get(self, **kwargs: Any) -> base.RESTObject: GitlabAuthenticationError: If authentication is not correct GitlabGetError: If the server cannot perform the request """ - if TYPE_CHECKING: - assert self.path is not None server_data = self.gitlab.http_get(self.path, **kwargs) if TYPE_CHECKING: assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None return self._obj_cls(self, server_data) @@ -167,7 +142,7 @@ class RefreshMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @exc.on_http_error(exc.GitlabGetError) def refresh(self, **kwargs: Any) -> None: @@ -194,18 +169,11 @@ def refresh(self, **kwargs: Any) -> None: self._update_attrs(server_data) -class ListMixin(HeadMixin, _RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] +class ListMixin(HeadMixin[base.TObjCls]): _list_filters: Tuple[str, ...] = () - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab @exc.on_http_error(exc.GitlabListError) - def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject]]: + def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.TObjCls]]: """Retrieve a list of objects. Args: @@ -244,37 +212,20 @@ def list(self, **kwargs: Any) -> Union[base.RESTObjectList, List[base.RESTObject # Allow to overwrite the path, handy for custom listings path = data.pop("path", self.path) - if TYPE_CHECKING: - assert self._obj_cls is not None obj = self.gitlab.http_list(path, **data) if isinstance(obj, list): return [self._obj_cls(self, item, created_from_list=True) for item in obj] return base.RESTObjectList(self, self._obj_cls, obj) -class RetrieveMixin(ListMixin, GetMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - +class RetrieveMixin(ListMixin[base.TObjCls], GetMixin[base.TObjCls]): ... -class CreateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab +class CreateMixin(base.RESTManager[base.TObjCls]): @exc.on_http_error(exc.GitlabCreateError) def create( self, data: Optional[Dict[str, Any]] = None, **kwargs: Any - ) -> base.RESTObject: + ) -> base.TObjCls: """Create a new object. Args: @@ -303,7 +254,6 @@ def create( server_data = self.gitlab.http_post(path, post_data=data, files=files, **kwargs) if TYPE_CHECKING: assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None return self._obj_cls(self, server_data) @@ -314,19 +264,13 @@ class UpdateMethod(enum.IntEnum): PATCH = 3 -class UpdateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] +class UpdateMixin(base.RESTManager[base.TObjCls]): + # Update mixins attrs for easier implementation _update_method: UpdateMethod = UpdateMethod.PUT - gitlab: gitlab.Gitlab def _get_update_method( self, - ) -> Callable[..., Union[Dict[str, Any], requests.Response]]: + ) -> Callable[..., Union[Dict[str, Any], "requests.Response"]]: """Return the HTTP method to use. Returns: @@ -384,17 +328,9 @@ def update( return result -class SetMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - +class SetMixin(base.RESTManager[base.TObjCls]): @exc.on_http_error(exc.GitlabSetError) - def set(self, key: str, value: str, **kwargs: Any) -> base.RESTObject: + def set(self, key: str, value: str, **kwargs: Any) -> base.TObjCls: """Create or update the object. Args: @@ -414,19 +350,10 @@ def set(self, key: str, value: str, **kwargs: Any) -> base.RESTObject: server_data = self.gitlab.http_put(path, post_data=data, **kwargs) if TYPE_CHECKING: assert not isinstance(server_data, requests.Response) - assert self._obj_cls is not None return self._obj_cls(self, server_data) -class DeleteMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - +class DeleteMixin(base.RESTManager[base.TObjCls]): @exc.on_http_error(exc.GitlabDeleteError) def delete(self, id: Optional[Union[str, int]] = None, **kwargs: Any) -> None: """Delete an object on the server. @@ -444,29 +371,24 @@ def delete(self, id: Optional[Union[str, int]] = None, **kwargs: Any) -> None: else: path = f"{self.path}/{utils.EncodedId(id)}" - if TYPE_CHECKING: - assert path is not None self.gitlab.http_delete(path, **kwargs) -class CRUDMixin(GetMixin, ListMixin, CreateMixin, UpdateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab +class CRUDMixin( + GetMixin[base.TObjCls], + ListMixin[base.TObjCls], + CreateMixin[base.TObjCls], + UpdateMixin[base.TObjCls], + DeleteMixin[base.TObjCls], +): ... -class NoUpdateMixin(GetMixin, ListMixin, CreateMixin, DeleteMixin): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab +class NoUpdateMixin( + GetMixin[base.TObjCls], + ListMixin[base.TObjCls], + CreateMixin[base.TObjCls], + DeleteMixin[base.TObjCls], +): ... class SaveMixin(_RestObjectBase): @@ -477,7 +399,7 @@ class SaveMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] def _get_updated_data(self) -> Dict[str, Any]: updated_data = {} @@ -526,7 +448,7 @@ class ObjectDeleteMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] def delete(self, **kwargs: Any) -> None: """Delete the object from the server. @@ -550,7 +472,7 @@ class UserAgentDetailMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action(cls_names=("Snippet", "ProjectSnippet", "ProjectIssue")) @exc.on_http_error(exc.GitlabGetError) @@ -577,7 +499,7 @@ class AccessRequestMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action( cls_names=("ProjectAccessRequest", "GroupAccessRequest"), @@ -612,7 +534,7 @@ class DownloadMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @overload def download( @@ -689,15 +611,7 @@ def download( ) -class RotateMixin(_RestManagerBase): - _computed_path: Optional[str] - _from_parent_attrs: Dict[str, Any] - _obj_cls: Optional[Type[base.RESTObject]] - _parent: Optional[base.RESTObject] - _parent_attrs: Dict[str, Any] - _path: Optional[str] - gitlab: gitlab.Gitlab - +class RotateMixin(base.RESTManager[base.TObjCls]): @cli.register_custom_action( cls_names=( "PersonalAccessTokenManager", @@ -708,7 +622,10 @@ class RotateMixin(_RestManagerBase): ) @exc.on_http_error(exc.GitlabRotateError) def rotate( - self, id: Union[str, int], expires_at: Optional[str] = None, **kwargs: Any + self, + id: Union[str, int], + expires_at: Optional[str] = None, + **kwargs: Any, ) -> Dict[str, Any]: """Rotate an access token. @@ -737,7 +654,7 @@ class ObjectRotateMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action( cls_names=("PersonalAccessToken", "GroupAccessToken", "ProjectAccessToken"), @@ -768,7 +685,7 @@ class SubscribableMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action( cls_names=("ProjectIssue", "ProjectMergeRequest", "ProjectLabel", "GroupLabel") @@ -817,7 +734,7 @@ class TodoMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTodoError) @@ -841,7 +758,7 @@ class TimeTrackingMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action(cls_names=("ProjectIssue", "ProjectMergeRequest")) @exc.on_http_error(exc.GitlabTimeTrackingError) @@ -956,7 +873,7 @@ class ParticipantsMixin(_RestObjectBase): _module: ModuleType _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] - manager: base.RESTManager + manager: base.RESTManager[Any] @cli.register_custom_action(cls_names=("ProjectMergeRequest", "ProjectIssue")) @exc.on_http_error(exc.GitlabListError) @@ -986,7 +903,7 @@ def participants( return result -class BadgeRenderMixin(_RestManagerBase): +class BadgeRenderMixin(base.RESTManager[base.TObjCls]): @cli.register_custom_action( cls_names=("GroupBadgeManager", "ProjectBadgeManager"), required=("link_url", "image_url"), @@ -1022,7 +939,7 @@ class PromoteMixin(_RestObjectBase): _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] _update_method: UpdateMethod = UpdateMethod.PUT - manager: base.RESTManager + manager: base.RESTManager[Any] def _get_update_method( self, @@ -1069,7 +986,7 @@ class UploadMixin(_RestObjectBase): _parent_attrs: Dict[str, Any] _updated_attrs: Dict[str, Any] _upload_path: str - manager: base.RESTManager + manager: base.RESTManager[Any] def _get_upload_path(self) -> str: """Formats _upload_path with object attributes. diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py index 8192f9558..a7a7fb6b6 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -28,25 +28,16 @@ def __init__( self.gl = gl self.args = args self.parent_args: Dict[str, Any] = {} - self.mgr_cls: Union[ - Type[gitlab.mixins.CreateMixin], - Type[gitlab.mixins.DeleteMixin], - Type[gitlab.mixins.GetMixin], - Type[gitlab.mixins.GetWithoutIdMixin], - Type[gitlab.mixins.ListMixin], - Type[gitlab.mixins.UpdateMixin], - ] = getattr(gitlab.v4.objects, f"{self.cls.__name__}Manager") + self.mgr_cls: Any = getattr(gitlab.v4.objects, f"{self.cls.__name__}Manager") # We could do something smart, like splitting the manager name to find # parents, build the chain of managers to get to the final object. # Instead we do something ugly and efficient: interpolate variables in # the class _path attribute, and replace the value with the result. - if TYPE_CHECKING: - assert self.mgr_cls._path is not None self._process_from_parent_attrs() self.mgr_cls._path = self.mgr_cls._path.format(**self.parent_args) - self.mgr = self.mgr_cls(gl) + self.mgr: Any = self.mgr_cls(gl) self.mgr._from_parent_attrs = self.parent_args if self.mgr_cls._types: for attr_name, type_cls in self.mgr_cls._types.items(): @@ -82,7 +73,9 @@ def run(self) -> Any: return self.do_custom() def do_custom(self) -> Any: - class_instance: Union[gitlab.base.RESTManager, gitlab.base.RESTObject] + class_instance: Union[ + gitlab.base.RESTManager[gitlab.base.RESTObject], gitlab.base.RESTObject + ] in_obj = cli.custom_actions[self.cls_name][self.resource_action].in_object # Get the object (lazy), then act @@ -132,6 +125,8 @@ def do_create(self) -> gitlab.base.RESTObject: assert isinstance(self.mgr, gitlab.mixins.CreateMixin) try: result = self.mgr.create(self.args) + if TYPE_CHECKING: + assert isinstance(result, gitlab.base.RESTObject) except Exception as e: # pragma: no cover, cli.die is unit-tested cli.die("Impossible to create object", e) return result @@ -159,6 +154,8 @@ def do_get(self) -> Optional[gitlab.base.RESTObject]: if isinstance(self.mgr, gitlab.mixins.GetWithoutIdMixin): try: result = self.mgr.get(id=None, **self.args) + if TYPE_CHECKING: + assert isinstance(result, gitlab.base.RESTObject) or result is None except Exception as e: # pragma: no cover, cli.die is unit-tested cli.die("Impossible to get object", e) return result @@ -170,6 +167,8 @@ def do_get(self) -> Optional[gitlab.base.RESTObject]: id = self.args.pop(self.cls._id_attr) try: result = self.mgr.get(id, lazy=False, **self.args) + if TYPE_CHECKING: + assert isinstance(result, gitlab.base.RESTObject) or result is None except Exception as e: # pragma: no cover, cli.die is unit-tested cli.die("Impossible to get object", e) return result @@ -401,10 +400,15 @@ def extend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: if not isinstance(cls, type): continue if issubclass(cls, gitlab.base.RESTManager): - if cls._obj_cls is not None: - classes.add(cls._obj_cls) + classes.add(cls._obj_cls) for cls in sorted(classes, key=operator.attrgetter("__name__")): + if cls is gitlab.base.RESTObject: + # Skip managers where _obj_cls is a plain RESTObject class + # Those managers do not actually manage any objects and + # can only be used to calls specific API paths. + continue + arg_name = cli.cls_to_gitlab_resource(cls) mgr_cls_name = f"{cls.__name__}Manager" mgr_cls = getattr(gitlab.v4.objects, mgr_cls_name) diff --git a/gitlab/v4/objects/access_requests.py b/gitlab/v4/objects/access_requests.py index e70eb276a..774f4cd25 100644 --- a/gitlab/v4/objects/access_requests.py +++ b/gitlab/v4/objects/access_requests.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( AccessRequestMixin, CreateMixin, @@ -19,7 +19,11 @@ class GroupAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): pass -class GroupAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class GroupAccessRequestManager( + ListMixin[GroupAccessRequest], + CreateMixin[GroupAccessRequest], + DeleteMixin[GroupAccessRequest], +): _path = "/groups/{group_id}/access_requests" _obj_cls = GroupAccessRequest _from_parent_attrs = {"group_id": "id"} @@ -29,7 +33,11 @@ class ProjectAccessRequest(AccessRequestMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectAccessRequestManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class ProjectAccessRequestManager( + ListMixin[ProjectAccessRequest], + CreateMixin[ProjectAccessRequest], + DeleteMixin[ProjectAccessRequest], +): _path = "/projects/{project_id}/access_requests" _obj_cls = ProjectAccessRequest _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/appearance.py b/gitlab/v4/objects/appearance.py index e55046014..cdc99ad2c 100644 --- a/gitlab/v4/objects/appearance.py +++ b/gitlab/v4/objects/appearance.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Optional, Union from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -15,7 +15,9 @@ class ApplicationAppearance(SaveMixin, RESTObject): _id_attr = None -class ApplicationAppearanceManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ApplicationAppearanceManager( + GetWithoutIdMixin[ApplicationAppearance], UpdateMixin[ApplicationAppearance] +): _path = "/application/appearance" _obj_cls = ApplicationAppearance _update_attrs = RequiredOptional( diff --git a/gitlab/v4/objects/applications.py b/gitlab/v4/objects/applications.py index 921bd0e08..8f2edfc94 100644 --- a/gitlab/v4/objects/applications.py +++ b/gitlab/v4/objects/applications.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin from gitlab.types import RequiredOptional @@ -13,7 +13,9 @@ class Application(ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class ApplicationManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class ApplicationManager( + ListMixin[Application], CreateMixin[Application], DeleteMixin[Application] +): _path = "/applications" _obj_cls = Application _create_attrs = RequiredOptional( diff --git a/gitlab/v4/objects/artifacts.py b/gitlab/v4/objects/artifacts.py index 99a231e0f..45a40322b 100644 --- a/gitlab/v4/objects/artifacts.py +++ b/gitlab/v4/objects/artifacts.py @@ -30,7 +30,7 @@ class ProjectArtifact(RESTObject): _id_attr = "ref_name" -class ProjectArtifactManager(RESTManager): +class ProjectArtifactManager(RESTManager[ProjectArtifact]): _obj_cls = ProjectArtifact _path = "/projects/{project_id}/jobs/artifacts" _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/audit_events.py b/gitlab/v4/objects/audit_events.py index c3ff35498..2f4f93f25 100644 --- a/gitlab/v4/objects/audit_events.py +++ b/gitlab/v4/objects/audit_events.py @@ -3,7 +3,7 @@ https://docs.gitlab.com/ee/api/audit_events.html """ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import RetrieveMixin __all__ = [ @@ -22,7 +22,7 @@ class AuditEvent(RESTObject): _id_attr = "id" -class AuditEventManager(RetrieveMixin, RESTManager): +class AuditEventManager(RetrieveMixin[AuditEvent]): _path = "/audit_events" _obj_cls = AuditEvent _list_filters = ("created_after", "created_before", "entity_type", "entity_id") @@ -32,7 +32,7 @@ class GroupAuditEvent(RESTObject): _id_attr = "id" -class GroupAuditEventManager(RetrieveMixin, RESTManager): +class GroupAuditEventManager(RetrieveMixin[GroupAuditEvent]): _path = "/groups/{group_id}/audit_events" _obj_cls = GroupAuditEvent _from_parent_attrs = {"group_id": "id"} @@ -43,7 +43,7 @@ class ProjectAuditEvent(RESTObject): _id_attr = "id" -class ProjectAuditEventManager(RetrieveMixin, RESTManager): +class ProjectAuditEventManager(RetrieveMixin[ProjectAuditEvent]): _path = "/projects/{project_id}/audit_events" _obj_cls = ProjectAuditEvent _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/award_emojis.py b/gitlab/v4/objects/award_emojis.py index f9153e726..4bcc4b2e9 100644 --- a/gitlab/v4/objects/award_emojis.py +++ b/gitlab/v4/objects/award_emojis.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin from gitlab.types import RequiredOptional @@ -26,7 +26,7 @@ class GroupEpicAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class GroupEpicAwardEmojiManager(NoUpdateMixin, RESTManager): +class GroupEpicAwardEmojiManager(NoUpdateMixin[GroupEpicAwardEmoji]): _path = "/groups/{group_id}/epics/{epic_iid}/award_emoji" _obj_cls = GroupEpicAwardEmoji _from_parent_attrs = {"group_id": "group_id", "epic_iid": "iid"} @@ -37,7 +37,7 @@ class GroupEpicNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class GroupEpicNoteAwardEmojiManager(NoUpdateMixin, RESTManager): +class GroupEpicNoteAwardEmojiManager(NoUpdateMixin[GroupEpicNoteAwardEmoji]): _path = "/groups/{group_id}/epics/{epic_iid}/notes/{note_id}/award_emoji" _obj_cls = GroupEpicNoteAwardEmoji _from_parent_attrs = { @@ -52,7 +52,7 @@ class ProjectIssueAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectIssueAwardEmojiManager(NoUpdateMixin[ProjectIssueAwardEmoji]): _path = "/projects/{project_id}/issues/{issue_iid}/award_emoji" _obj_cls = ProjectIssueAwardEmoji _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -63,7 +63,7 @@ class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin[ProjectIssueNoteAwardEmoji]): _path = "/projects/{project_id}/issues/{issue_iid}/notes/{note_id}/award_emoji" _obj_cls = ProjectIssueNoteAwardEmoji _from_parent_attrs = { @@ -78,7 +78,9 @@ class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectMergeRequestAwardEmojiManager( + NoUpdateMixin[ProjectMergeRequestAwardEmoji] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/award_emoji" _obj_cls = ProjectMergeRequestAwardEmoji _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -89,7 +91,9 @@ class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectMergeRequestNoteAwardEmojiManager( + NoUpdateMixin[ProjectMergeRequestNoteAwardEmoji] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/notes/{note_id}/award_emoji" _obj_cls = ProjectMergeRequestNoteAwardEmoji _from_parent_attrs = { @@ -104,7 +108,7 @@ class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectSnippetAwardEmojiManager(NoUpdateMixin[ProjectSnippetAwardEmoji]): _path = "/projects/{project_id}/snippets/{snippet_id}/award_emoji" _obj_cls = ProjectSnippetAwardEmoji _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} @@ -115,7 +119,7 @@ class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass -class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager): +class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin[ProjectSnippetNoteAwardEmoji]): _path = "/projects/{project_id}/snippets/{snippet_id}/notes/{note_id}/award_emoji" _obj_cls = ProjectSnippetNoteAwardEmoji _from_parent_attrs = { diff --git a/gitlab/v4/objects/badges.py b/gitlab/v4/objects/badges.py index f1b262569..79c691c67 100644 --- a/gitlab/v4/objects/badges.py +++ b/gitlab/v4/objects/badges.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import BadgeRenderMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -14,7 +14,7 @@ class GroupBadge(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class GroupBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): +class GroupBadgeManager(BadgeRenderMixin[GroupBadge], CRUDMixin[GroupBadge]): _path = "/groups/{group_id}/badges" _obj_cls = GroupBadge _from_parent_attrs = {"group_id": "id"} @@ -26,7 +26,7 @@ class ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): +class ProjectBadgeManager(BadgeRenderMixin[ProjectBadge], CRUDMixin[ProjectBadge]): _path = "/projects/{project_id}/badges" _obj_cls = ProjectBadge _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py index e19ae3335..861b09046 100644 --- a/gitlab/v4/objects/boards.py +++ b/gitlab/v4/objects/boards.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -18,7 +18,7 @@ class GroupBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class GroupBoardListManager(CRUDMixin, RESTManager): +class GroupBoardListManager(CRUDMixin[GroupBoardList]): _path = "/groups/{group_id}/boards/{board_id}/lists" _obj_cls = GroupBoardList _from_parent_attrs = {"group_id": "group_id", "board_id": "id"} @@ -32,7 +32,7 @@ class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject): lists: GroupBoardListManager -class GroupBoardManager(CRUDMixin, RESTManager): +class GroupBoardManager(CRUDMixin[GroupBoard]): _path = "/groups/{group_id}/boards" _obj_cls = GroupBoard _from_parent_attrs = {"group_id": "id"} @@ -43,7 +43,7 @@ class ProjectBoardList(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectBoardListManager(CRUDMixin, RESTManager): +class ProjectBoardListManager(CRUDMixin[ProjectBoardList]): _path = "/projects/{project_id}/boards/{board_id}/lists" _obj_cls = ProjectBoardList _from_parent_attrs = {"project_id": "project_id", "board_id": "id"} @@ -57,7 +57,7 @@ class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject): lists: ProjectBoardListManager -class ProjectBoardManager(CRUDMixin, RESTManager): +class ProjectBoardManager(CRUDMixin[ProjectBoard]): _path = "/projects/{project_id}/boards" _obj_cls = ProjectBoard _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py index 22d112e2a..0724476a6 100644 --- a/gitlab/v4/objects/branches.py +++ b/gitlab/v4/objects/branches.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CRUDMixin, NoUpdateMixin, @@ -20,7 +20,7 @@ class ProjectBranch(ObjectDeleteMixin, RESTObject): _id_attr = "name" -class ProjectBranchManager(NoUpdateMixin, RESTManager): +class ProjectBranchManager(NoUpdateMixin[ProjectBranch]): _path = "/projects/{project_id}/repository/branches" _obj_cls = ProjectBranch _from_parent_attrs = {"project_id": "id"} @@ -31,7 +31,7 @@ class ProjectProtectedBranch(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "name" -class ProjectProtectedBranchManager(CRUDMixin, RESTManager): +class ProjectProtectedBranchManager(CRUDMixin[ProjectProtectedBranch]): _path = "/projects/{project_id}/protected_branches" _obj_cls = ProjectProtectedBranch _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/broadcast_messages.py b/gitlab/v4/objects/broadcast_messages.py index 492c65bf2..42806b4dd 100644 --- a/gitlab/v4/objects/broadcast_messages.py +++ b/gitlab/v4/objects/broadcast_messages.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import ArrayAttribute, RequiredOptional @@ -12,7 +12,7 @@ class BroadcastMessage(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class BroadcastMessageManager(CRUDMixin, RESTManager): +class BroadcastMessageManager(CRUDMixin[BroadcastMessage]): _path = "/broadcast_messages" _obj_cls = BroadcastMessage diff --git a/gitlab/v4/objects/bulk_imports.py b/gitlab/v4/objects/bulk_imports.py index dfbee1880..a08a228ae 100644 --- a/gitlab/v4/objects/bulk_imports.py +++ b/gitlab/v4/objects/bulk_imports.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin from gitlab.types import RequiredOptional @@ -16,7 +16,7 @@ class BulkImport(RefreshMixin, RESTObject): entities: "BulkImportEntityManager" -class BulkImportManager(CreateMixin, RetrieveMixin, RESTManager): +class BulkImportManager(CreateMixin[BulkImport], RetrieveMixin[BulkImport]): _path = "/bulk_imports" _obj_cls = BulkImport _create_attrs = RequiredOptional(required=("configuration", "entities")) @@ -27,7 +27,7 @@ class BulkImportEntity(RefreshMixin, RESTObject): pass -class BulkImportEntityManager(RetrieveMixin, RESTManager): +class BulkImportEntityManager(RetrieveMixin[BulkImportEntity]): _path = "/bulk_imports/{bulk_import_id}/entities" _obj_cls = BulkImportEntity _from_parent_attrs = {"bulk_import_id": "id"} @@ -38,7 +38,7 @@ class BulkImportAllEntity(RESTObject): pass -class BulkImportAllEntityManager(ListMixin, RESTManager): +class BulkImportAllEntityManager(ListMixin[BulkImportAllEntity]): _path = "/bulk_imports/entities" _obj_cls = BulkImportAllEntity _list_filters = ("sort", "status") diff --git a/gitlab/v4/objects/ci_lint.py b/gitlab/v4/objects/ci_lint.py index 5fa146fac..e9c2a26c2 100644 --- a/gitlab/v4/objects/ci_lint.py +++ b/gitlab/v4/objects/ci_lint.py @@ -5,7 +5,7 @@ from typing import Any -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.cli import register_custom_action from gitlab.exceptions import GitlabCiLintError from gitlab.mixins import CreateMixin, GetWithoutIdMixin @@ -23,7 +23,7 @@ class CiLint(RESTObject): _id_attr = None -class CiLintManager(CreateMixin, RESTManager): +class CiLintManager(CreateMixin[CiLint]): _path = "/ci/lint" _obj_cls = CiLint _create_attrs = RequiredOptional( @@ -50,7 +50,9 @@ class ProjectCiLint(RESTObject): _id_attr = None -class ProjectCiLintManager(GetWithoutIdMixin, CreateMixin, RESTManager): +class ProjectCiLintManager( + GetWithoutIdMixin[ProjectCiLint], CreateMixin[ProjectCiLint] +): _path = "/projects/{project_id}/ci/lint" _obj_cls = ProjectCiLint _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/cluster_agents.py b/gitlab/v4/objects/cluster_agents.py index c53c8ab5f..a1a60408c 100644 --- a/gitlab/v4/objects/cluster_agents.py +++ b/gitlab/v4/objects/cluster_agents.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -12,7 +12,7 @@ class ProjectClusterAgent(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class ProjectClusterAgentManager(NoUpdateMixin, RESTManager): +class ProjectClusterAgentManager(NoUpdateMixin[ProjectClusterAgent]): _path = "/projects/{project_id}/cluster_agents" _obj_cls = ProjectClusterAgent _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/clusters.py b/gitlab/v4/objects/clusters.py index ead012c61..4f3f2d5a1 100644 --- a/gitlab/v4/objects/clusters.py +++ b/gitlab/v4/objects/clusters.py @@ -1,8 +1,8 @@ -from typing import Any, cast, Dict, Optional +from typing import Any, Dict, Optional from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject -from gitlab.mixins import CreateMixin, CRUDMixin, ObjectDeleteMixin, SaveMixin +from gitlab.base import RESTObject +from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional __all__ = [ @@ -17,7 +17,7 @@ class GroupCluster(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class GroupClusterManager(CRUDMixin, RESTManager): +class GroupClusterManager(CRUDMixin[GroupCluster]): _path = "/groups/{group_id}/clusters" _obj_cls = GroupCluster _from_parent_attrs = {"group_id": "id"} @@ -56,14 +56,14 @@ def create( the data sent by the server """ path = f"{self.path}/user" - return cast(GroupCluster, CreateMixin.create(self, data, path=path, **kwargs)) + return super().create(data, path=path, **kwargs) class ProjectCluster(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectClusterManager(CRUDMixin, RESTManager): +class ProjectClusterManager(CRUDMixin[ProjectCluster]): _path = "/projects/{project_id}/clusters" _obj_cls = ProjectCluster _from_parent_attrs = {"project_id": "id"} @@ -102,4 +102,4 @@ def create( the data sent by the server """ path = f"{self.path}/user" - return cast(ProjectCluster, CreateMixin.create(self, data, path=path, **kwargs)) + return super().create(data, path=path, **kwargs) diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py index 935a3bc28..7957922b4 100644 --- a/gitlab/v4/objects/commits.py +++ b/gitlab/v4/objects/commits.py @@ -1,11 +1,11 @@ -from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union import requests import gitlab from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, ListMixin, RefreshMixin, RetrieveMixin from gitlab.types import RequiredOptional @@ -169,7 +169,7 @@ def signature(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: return self.manager.gitlab.http_get(path, **kwargs) -class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager): +class ProjectCommitManager(RetrieveMixin[ProjectCommit], CreateMixin[ProjectCommit]): _path = "/projects/{project_id}/repository/commits" _obj_cls = ProjectCommit _from_parent_attrs = {"project_id": "id"} @@ -195,7 +195,9 @@ class ProjectCommitComment(RESTObject): _repr_attr = "note" -class ProjectCommitCommentManager(ListMixin, CreateMixin, RESTManager): +class ProjectCommitCommentManager( + ListMixin[ProjectCommitComment], CreateMixin[ProjectCommitComment] +): _path = "/projects/{project_id}/repository/commits/{commit_id}/comments" _obj_cls = ProjectCommitComment _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} @@ -208,7 +210,9 @@ class ProjectCommitStatus(RefreshMixin, RESTObject): pass -class ProjectCommitStatusManager(ListMixin, CreateMixin, RESTManager): +class ProjectCommitStatusManager( + ListMixin[ProjectCommitStatus], CreateMixin[ProjectCommitStatus] +): _path = "/projects/{project_id}/repository/commits/{commit_id}/statuses" _obj_cls = ProjectCommitStatus _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} @@ -248,6 +252,4 @@ def create( path = self._compute_path(base_path) if TYPE_CHECKING: assert path is not None - return cast( - ProjectCommitStatus, CreateMixin.create(self, data, path=path, **kwargs) - ) + return super().create(data, path=path, **kwargs) diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py index 08b3ca072..cb7904ee4 100644 --- a/gitlab/v4/objects/container_registry.py +++ b/gitlab/v4/objects/container_registry.py @@ -1,8 +1,8 @@ -from typing import Any, TYPE_CHECKING +from typing import Any from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( DeleteMixin, GetMixin, @@ -26,7 +26,9 @@ class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): tags: "ProjectRegistryTagManager" -class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager): +class ProjectRegistryRepositoryManager( + DeleteMixin[ProjectRegistryRepository], ListMixin[ProjectRegistryRepository] +): _path = "/projects/{project_id}/registry/repositories" _obj_cls = ProjectRegistryRepository _from_parent_attrs = {"project_id": "id"} @@ -36,7 +38,9 @@ class ProjectRegistryTag(ObjectDeleteMixin, RESTObject): _id_attr = "name" -class ProjectRegistryTagManager(DeleteMixin, RetrieveMixin, RESTManager): +class ProjectRegistryTagManager( + DeleteMixin[ProjectRegistryTag], RetrieveMixin[ProjectRegistryTag] +): _obj_cls = ProjectRegistryTag _from_parent_attrs = {"project_id": "project_id", "repository_id": "id"} _path = "/projects/{project_id}/registry/repositories/{repository_id}/tags" @@ -66,12 +70,10 @@ def delete_in_bulk(self, name_regex_delete: str, **kwargs: Any) -> None: valid_attrs = ["keep_n", "name_regex_keep", "older_than"] data = {"name_regex_delete": name_regex_delete} data.update({k: v for k, v in kwargs.items() if k in valid_attrs}) - if TYPE_CHECKING: - assert self.path is not None self.gitlab.http_delete(self.path, query_data=data, **kwargs) -class GroupRegistryRepositoryManager(ListMixin, RESTManager): +class GroupRegistryRepositoryManager(ListMixin[ProjectRegistryRepository]): _path = "/groups/{group_id}/registry/repositories" _obj_cls = ProjectRegistryRepository _from_parent_attrs = {"group_id": "id"} @@ -81,6 +83,6 @@ class RegistryRepository(RESTObject): _repr_attr = "path" -class RegistryRepositoryManager(GetMixin, RESTManager): +class RegistryRepositoryManager(GetMixin[RegistryRepository]): _path = "/registry/repositories" _obj_cls = RegistryRepository diff --git a/gitlab/v4/objects/custom_attributes.py b/gitlab/v4/objects/custom_attributes.py index aed19652f..94b2c1722 100644 --- a/gitlab/v4/objects/custom_attributes.py +++ b/gitlab/v4/objects/custom_attributes.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import DeleteMixin, ObjectDeleteMixin, RetrieveMixin, SetMixin __all__ = [ @@ -15,7 +15,11 @@ class GroupCustomAttribute(ObjectDeleteMixin, RESTObject): _id_attr = "key" -class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): +class GroupCustomAttributeManager( + RetrieveMixin[GroupCustomAttribute], + SetMixin[GroupCustomAttribute], + DeleteMixin[GroupCustomAttribute], +): _path = "/groups/{group_id}/custom_attributes" _obj_cls = GroupCustomAttribute _from_parent_attrs = {"group_id": "id"} @@ -25,7 +29,11 @@ class ProjectCustomAttribute(ObjectDeleteMixin, RESTObject): _id_attr = "key" -class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): +class ProjectCustomAttributeManager( + RetrieveMixin[ProjectCustomAttribute], + SetMixin[ProjectCustomAttribute], + DeleteMixin[ProjectCustomAttribute], +): _path = "/projects/{project_id}/custom_attributes" _obj_cls = ProjectCustomAttribute _from_parent_attrs = {"project_id": "id"} @@ -35,7 +43,11 @@ class UserCustomAttribute(ObjectDeleteMixin, RESTObject): _id_attr = "key" -class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManager): +class UserCustomAttributeManager( + RetrieveMixin[UserCustomAttribute], + SetMixin[UserCustomAttribute], + DeleteMixin[UserCustomAttribute], +): _path = "/users/{user_id}/custom_attributes" _obj_cls = UserCustomAttribute _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/deploy_keys.py b/gitlab/v4/objects/deploy_keys.py index 44fa51007..c959357b4 100644 --- a/gitlab/v4/objects/deploy_keys.py +++ b/gitlab/v4/objects/deploy_keys.py @@ -4,7 +4,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ListMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -20,7 +20,7 @@ class DeployKey(RESTObject): pass -class DeployKeyManager(ListMixin, RESTManager): +class DeployKeyManager(ListMixin[DeployKey]): _path = "/deploy_keys" _obj_cls = DeployKey @@ -29,7 +29,7 @@ class ProjectKey(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectKeyManager(CRUDMixin, RESTManager): +class ProjectKeyManager(CRUDMixin[ProjectKey]): _path = "/projects/{project_id}/deploy_keys" _obj_cls = ProjectKey _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/deploy_tokens.py b/gitlab/v4/objects/deploy_tokens.py index 4c283789d..38426f6bc 100644 --- a/gitlab/v4/objects/deploy_tokens.py +++ b/gitlab/v4/objects/deploy_tokens.py @@ -1,5 +1,5 @@ from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -23,7 +23,7 @@ class DeployToken(ObjectDeleteMixin, RESTObject): pass -class DeployTokenManager(ListMixin, RESTManager): +class DeployTokenManager(ListMixin[DeployToken]): _path = "/deploy_tokens" _obj_cls = DeployToken @@ -32,7 +32,11 @@ class GroupDeployToken(ObjectDeleteMixin, RESTObject): pass -class GroupDeployTokenManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class GroupDeployTokenManager( + RetrieveMixin[GroupDeployToken], + CreateMixin[GroupDeployToken], + DeleteMixin[GroupDeployToken], +): _path = "/groups/{group_id}/deploy_tokens" _from_parent_attrs = {"group_id": "id"} _obj_cls = GroupDeployToken @@ -54,7 +58,11 @@ class ProjectDeployToken(ObjectDeleteMixin, RESTObject): pass -class ProjectDeployTokenManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class ProjectDeployTokenManager( + RetrieveMixin[ProjectDeployToken], + CreateMixin[ProjectDeployToken], + DeleteMixin[ProjectDeployToken], +): _path = "/projects/{project_id}/deploy_tokens" _from_parent_attrs = {"project_id": "id"} _obj_cls = ProjectDeployToken diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py index 7e080d082..2c0de96cd 100644 --- a/gitlab/v4/objects/deployments.py +++ b/gitlab/v4/objects/deployments.py @@ -7,7 +7,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -67,7 +67,11 @@ def approval( return server_data -class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager): +class ProjectDeploymentManager( + RetrieveMixin[ProjectDeployment], + CreateMixin[ProjectDeployment], + UpdateMixin[ProjectDeployment], +): _path = "/projects/{project_id}/deployments" _obj_cls = ProjectDeployment _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py index abcd40108..c43898b5e 100644 --- a/gitlab/v4/objects/discussions.py +++ b/gitlab/v4/objects/discussions.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -25,7 +25,9 @@ class ProjectCommitDiscussion(RESTObject): notes: ProjectCommitDiscussionNoteManager -class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): +class ProjectCommitDiscussionManager( + RetrieveMixin[ProjectCommitDiscussion], CreateMixin[ProjectCommitDiscussion] +): _path = "/projects/{project_id}/repository/commits/{commit_id}/discussions" _obj_cls = ProjectCommitDiscussion _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} @@ -36,7 +38,9 @@ class ProjectIssueDiscussion(RESTObject): notes: ProjectIssueDiscussionNoteManager -class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): +class ProjectIssueDiscussionManager( + RetrieveMixin[ProjectIssueDiscussion], CreateMixin[ProjectIssueDiscussion] +): _path = "/projects/{project_id}/issues/{issue_iid}/discussions" _obj_cls = ProjectIssueDiscussion _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -48,7 +52,9 @@ class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): class ProjectMergeRequestDiscussionManager( - RetrieveMixin, CreateMixin, UpdateMixin, RESTManager + RetrieveMixin[ProjectMergeRequestDiscussion], + CreateMixin[ProjectMergeRequestDiscussion], + UpdateMixin[ProjectMergeRequestDiscussion], ): _path = "/projects/{project_id}/merge_requests/{mr_iid}/discussions" _obj_cls = ProjectMergeRequestDiscussion @@ -63,7 +69,9 @@ class ProjectSnippetDiscussion(RESTObject): notes: ProjectSnippetDiscussionNoteManager -class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): +class ProjectSnippetDiscussionManager( + RetrieveMixin[ProjectSnippetDiscussion], CreateMixin[ProjectSnippetDiscussion] +): _path = "/projects/{project_id}/snippets/{snippet_id}/discussions" _obj_cls = ProjectSnippetDiscussion _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} diff --git a/gitlab/v4/objects/draft_notes.py b/gitlab/v4/objects/draft_notes.py index 19ea92cd7..8ceeda302 100644 --- a/gitlab/v4/objects/draft_notes.py +++ b/gitlab/v4/objects/draft_notes.py @@ -1,6 +1,6 @@ from typing import Any -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -16,7 +16,7 @@ def publish(self, **kwargs: Any) -> None: self.manager.gitlab.http_put(path, **kwargs) -class ProjectMergeRequestDraftNoteManager(CRUDMixin, RESTManager): +class ProjectMergeRequestDraftNoteManager(CRUDMixin[ProjectMergeRequestDraftNote]): _path = "/projects/{project_id}/merge_requests/{mr_iid}/draft_notes" _obj_cls = ProjectMergeRequestDraftNote _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py index 545f2fc7e..b69f3d713 100644 --- a/gitlab/v4/objects/environments.py +++ b/gitlab/v4/objects/environments.py @@ -4,7 +4,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -44,7 +44,10 @@ def stop(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: class ProjectEnvironmentManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + RetrieveMixin[ProjectEnvironment], + CreateMixin[ProjectEnvironment], + UpdateMixin[ProjectEnvironment], + DeleteMixin[ProjectEnvironment], ): _path = "/projects/{project_id}/environments" _obj_cls = ProjectEnvironment @@ -60,7 +63,9 @@ class ProjectProtectedEnvironment(ObjectDeleteMixin, RESTObject): class ProjectProtectedEnvironmentManager( - RetrieveMixin, CreateMixin, DeleteMixin, RESTManager + RetrieveMixin[ProjectProtectedEnvironment], + CreateMixin[ProjectProtectedEnvironment], + DeleteMixin[ProjectProtectedEnvironment], ): _path = "/projects/{project_id}/protected_environments" _obj_cls = ProjectProtectedEnvironment diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py index 8144e7b70..b882d57f3 100644 --- a/gitlab/v4/objects/epics.py +++ b/gitlab/v4/objects/epics.py @@ -2,7 +2,7 @@ from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -33,7 +33,7 @@ class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): notes: GroupEpicNoteManager -class GroupEpicManager(CRUDMixin, RESTManager): +class GroupEpicManager(CRUDMixin[GroupEpic]): _path = "/groups/{group_id}/epics" _obj_cls = GroupEpic _from_parent_attrs = {"group_id": "id"} @@ -77,7 +77,10 @@ def save(self, **kwargs: Any) -> None: class GroupEpicIssueManager( - ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + ListMixin[GroupEpicIssue], + CreateMixin[GroupEpicIssue], + UpdateMixin[GroupEpicIssue], + DeleteMixin[GroupEpicIssue], ): _path = "/groups/{group_id}/epics/{epic_iid}/issues" _obj_cls = GroupEpicIssue diff --git a/gitlab/v4/objects/events.py b/gitlab/v4/objects/events.py index ab94ec6bf..c9594ce34 100644 --- a/gitlab/v4/objects/events.py +++ b/gitlab/v4/objects/events.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ListMixin, RetrieveMixin __all__ = [ @@ -34,7 +34,7 @@ class Event(RESTObject): _repr_attr = "target_title" -class EventManager(ListMixin, RESTManager): +class EventManager(ListMixin[Event]): _path = "/events" _obj_cls = Event _list_filters = ("action", "target_type", "before", "after", "sort", "scope") @@ -44,7 +44,7 @@ class GroupEpicResourceLabelEvent(RESTObject): pass -class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): +class GroupEpicResourceLabelEventManager(RetrieveMixin[GroupEpicResourceLabelEvent]): _path = "/groups/{group_id}/epics/{epic_id}/resource_label_events" _obj_cls = GroupEpicResourceLabelEvent _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} @@ -64,7 +64,9 @@ class ProjectIssueResourceLabelEvent(RESTObject): pass -class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager): +class ProjectIssueResourceLabelEventManager( + RetrieveMixin[ProjectIssueResourceLabelEvent] +): _path = "/projects/{project_id}/issues/{issue_iid}/resource_label_events" _obj_cls = ProjectIssueResourceLabelEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -74,7 +76,9 @@ class ProjectIssueResourceMilestoneEvent(RESTObject): pass -class ProjectIssueResourceMilestoneEventManager(RetrieveMixin, RESTManager): +class ProjectIssueResourceMilestoneEventManager( + RetrieveMixin[ProjectIssueResourceMilestoneEvent] +): _path = "/projects/{project_id}/issues/{issue_iid}/resource_milestone_events" _obj_cls = ProjectIssueResourceMilestoneEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -84,7 +88,9 @@ class ProjectIssueResourceStateEvent(RESTObject): pass -class ProjectIssueResourceStateEventManager(RetrieveMixin, RESTManager): +class ProjectIssueResourceStateEventManager( + RetrieveMixin[ProjectIssueResourceStateEvent] +): _path = "/projects/{project_id}/issues/{issue_iid}/resource_state_events" _obj_cls = ProjectIssueResourceStateEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -94,7 +100,9 @@ class ProjectIssueResourceIterationEvent(RESTObject): pass -class ProjectIssueResourceIterationEventManager(RetrieveMixin, RESTManager): +class ProjectIssueResourceIterationEventManager( + RetrieveMixin[ProjectIssueResourceIterationEvent] +): _path = "/projects/{project_id}/issues/{issue_iid}/resource_iteration_events" _obj_cls = ProjectIssueResourceIterationEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -104,7 +112,9 @@ class ProjectIssueResourceWeightEvent(RESTObject): pass -class ProjectIssueResourceWeightEventManager(RetrieveMixin, RESTManager): +class ProjectIssueResourceWeightEventManager( + RetrieveMixin[ProjectIssueResourceWeightEvent] +): _path = "/projects/{project_id}/issues/{issue_iid}/resource_weight_events" _obj_cls = ProjectIssueResourceWeightEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -114,7 +124,9 @@ class ProjectMergeRequestResourceLabelEvent(RESTObject): pass -class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager): +class ProjectMergeRequestResourceLabelEventManager( + RetrieveMixin[ProjectMergeRequestResourceLabelEvent] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/resource_label_events" _obj_cls = ProjectMergeRequestResourceLabelEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -124,7 +136,9 @@ class ProjectMergeRequestResourceMilestoneEvent(RESTObject): pass -class ProjectMergeRequestResourceMilestoneEventManager(RetrieveMixin, RESTManager): +class ProjectMergeRequestResourceMilestoneEventManager( + RetrieveMixin[ProjectMergeRequestResourceMilestoneEvent] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/resource_milestone_events" _obj_cls = ProjectMergeRequestResourceMilestoneEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -134,7 +148,9 @@ class ProjectMergeRequestResourceStateEvent(RESTObject): pass -class ProjectMergeRequestResourceStateEventManager(RetrieveMixin, RESTManager): +class ProjectMergeRequestResourceStateEventManager( + RetrieveMixin[ProjectMergeRequestResourceStateEvent] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/resource_state_events" _obj_cls = ProjectMergeRequestResourceStateEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/export_import.py b/gitlab/v4/objects/export_import.py index a3768ed0f..fba2bc867 100644 --- a/gitlab/v4/objects/export_import.py +++ b/gitlab/v4/objects/export_import.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, DownloadMixin, GetWithoutIdMixin, RefreshMixin from gitlab.types import RequiredOptional @@ -18,7 +18,7 @@ class GroupExport(DownloadMixin, RESTObject): _id_attr = None -class GroupExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): +class GroupExportManager(GetWithoutIdMixin[GroupExport], CreateMixin[GroupExport]): _path = "/groups/{group_id}/export" _obj_cls = GroupExport _from_parent_attrs = {"group_id": "id"} @@ -28,7 +28,7 @@ class GroupImport(RESTObject): _id_attr = None -class GroupImportManager(GetWithoutIdMixin, RESTManager): +class GroupImportManager(GetWithoutIdMixin[GroupImport]): _path = "/groups/{group_id}/import" _obj_cls = GroupImport _from_parent_attrs = {"group_id": "id"} @@ -38,7 +38,9 @@ class ProjectExport(DownloadMixin, RefreshMixin, RESTObject): _id_attr = None -class ProjectExportManager(GetWithoutIdMixin, CreateMixin, RESTManager): +class ProjectExportManager( + GetWithoutIdMixin[ProjectExport], CreateMixin[ProjectExport] +): _path = "/projects/{project_id}/export" _obj_cls = ProjectExport _from_parent_attrs = {"project_id": "id"} @@ -49,7 +51,7 @@ class ProjectImport(RefreshMixin, RESTObject): _id_attr = None -class ProjectImportManager(GetWithoutIdMixin, RESTManager): +class ProjectImportManager(GetWithoutIdMixin[ProjectImport]): _path = "/projects/{project_id}/import" _obj_cls = ProjectImport _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/features.py b/gitlab/v4/objects/features.py index f68c10e8d..f6a76e910 100644 --- a/gitlab/v4/objects/features.py +++ b/gitlab/v4/objects/features.py @@ -7,7 +7,7 @@ from gitlab import exceptions as exc from gitlab import utils -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin __all__ = [ @@ -20,7 +20,7 @@ class Feature(ObjectDeleteMixin, RESTObject): _id_attr = "name" -class FeatureManager(ListMixin, DeleteMixin, RESTManager): +class FeatureManager(ListMixin[Feature], DeleteMixin[Feature]): _path = "/features/" _obj_cls = Feature diff --git a/gitlab/v4/objects/files.py b/gitlab/v4/objects/files.py index e1f7b2290..690946b52 100644 --- a/gitlab/v4/objects/files.py +++ b/gitlab/v4/objects/files.py @@ -18,7 +18,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import utils -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -97,7 +97,9 @@ def delete( # type: ignore[override] self.manager.delete(file_path, branch, commit_message, **kwargs) -class ProjectFileManager(CreateMixin, UpdateMixin, DeleteMixin, RESTManager): +class ProjectFileManager( + CreateMixin[ProjectFile], UpdateMixin[ProjectFile], DeleteMixin[ProjectFile] +): _path = "/projects/{project_id}/repository/files" _obj_cls = ProjectFile _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/geo_nodes.py b/gitlab/v4/objects/geo_nodes.py index 2745151db..00cb4dd97 100644 --- a/gitlab/v4/objects/geo_nodes.py +++ b/gitlab/v4/objects/geo_nodes.py @@ -2,7 +2,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( DeleteMixin, ObjectDeleteMixin, @@ -59,7 +59,9 @@ def status(self, **kwargs: Any) -> Dict[str, Any]: return result -class GeoNodeManager(RetrieveMixin, UpdateMixin, DeleteMixin, RESTManager): +class GeoNodeManager( + RetrieveMixin[GeoNode], UpdateMixin[GeoNode], DeleteMixin[GeoNode] +): _path = "/geo_nodes" _obj_cls = GeoNode _update_attrs = RequiredOptional( diff --git a/gitlab/v4/objects/group_access_tokens.py b/gitlab/v4/objects/group_access_tokens.py index 0b2251b80..06fff229c 100644 --- a/gitlab/v4/objects/group_access_tokens.py +++ b/gitlab/v4/objects/group_access_tokens.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -20,7 +20,10 @@ class GroupAccessToken(ObjectDeleteMixin, ObjectRotateMixin, RESTObject): class GroupAccessTokenManager( - CreateMixin, DeleteMixin, RetrieveMixin, RotateMixin, RESTManager + CreateMixin[GroupAccessToken], + DeleteMixin[GroupAccessToken], + RetrieveMixin[GroupAccessToken], + RotateMixin[GroupAccessToken], ): _path = "/groups/{group_id}/access_tokens" _obj_cls = GroupAccessToken diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index f9aeb0b19..82f09632e 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -1,4 +1,4 @@ -from typing import Any, BinaryIO, Dict, List, Optional, Type, TYPE_CHECKING, Union +from typing import Any, BinaryIO, Dict, List, Optional, TYPE_CHECKING, Union import requests @@ -6,7 +6,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject, TObjCls from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -253,7 +253,7 @@ def restore(self, **kwargs: Any) -> None: self.manager.gitlab.http_post(path, **kwargs) -class GroupManager(CRUDMixin, RESTManager): +class GroupManager(CRUDMixin[Group]): _path = "/groups" _obj_cls = Group _list_filters = ( @@ -355,13 +355,7 @@ def import_group( ) -class GroupSubgroup(RESTObject): - pass - - -class GroupSubgroupManager(ListMixin, RESTManager): - _path = "/groups/{group_id}/subgroups" - _obj_cls: Union[Type["GroupDescendantGroup"], Type[GroupSubgroup]] = GroupSubgroup +class SubgroupBaseManager(ListMixin[TObjCls]): _from_parent_attrs = {"group_id": "id"} _list_filters = ( "skip_groups", @@ -377,18 +371,27 @@ class GroupSubgroupManager(ListMixin, RESTManager): _types = {"skip_groups": types.ArrayAttribute} +class GroupSubgroup(RESTObject): + pass + + +class GroupSubgroupManager(SubgroupBaseManager[GroupSubgroup]): + _path = "/groups/{group_id}/subgroups" + _obj_cls = GroupSubgroup + + class GroupDescendantGroup(RESTObject): pass -class GroupDescendantGroupManager(GroupSubgroupManager): +class GroupDescendantGroupManager(SubgroupBaseManager[GroupDescendantGroup]): """ This manager inherits from GroupSubgroupManager as descendant groups share all attributes with subgroups, except the path and object class. """ _path = "/groups/{group_id}/descendant_groups" - _obj_cls: Type[GroupDescendantGroup] = GroupDescendantGroup + _obj_cls = GroupDescendantGroup class GroupLDAPGroupLink(RESTObject): @@ -423,9 +426,13 @@ def delete(self, **kwargs: Any) -> None: ) -class GroupLDAPGroupLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class GroupLDAPGroupLinkManager( + ListMixin[GroupLDAPGroupLink], + CreateMixin[GroupLDAPGroupLink], + DeleteMixin[GroupLDAPGroupLink], +): _path = "/groups/{group_id}/ldap_group_links" - _obj_cls: Type[GroupLDAPGroupLink] = GroupLDAPGroupLink + _obj_cls = GroupLDAPGroupLink _from_parent_attrs = {"group_id": "id"} _create_attrs = RequiredOptional( required=("provider", "group_access"), exclusive=("cn", "filter") @@ -437,8 +444,8 @@ class GroupSAMLGroupLink(ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class GroupSAMLGroupLinkManager(NoUpdateMixin, RESTManager): +class GroupSAMLGroupLinkManager(NoUpdateMixin[GroupSAMLGroupLink]): _path = "/groups/{group_id}/saml_group_links" - _obj_cls: Type[GroupSAMLGroupLink] = GroupSAMLGroupLink + _obj_cls = GroupSAMLGroupLink _from_parent_attrs = {"group_id": "id"} _create_attrs = RequiredOptional(required=("saml_group_name", "access_level")) diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py index 087d5759a..f9ce553bb 100644 --- a/gitlab/v4/objects/hooks.py +++ b/gitlab/v4/objects/hooks.py @@ -1,5 +1,5 @@ from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -18,7 +18,7 @@ class Hook(ObjectDeleteMixin, RESTObject): _repr_attr = "url" -class HookManager(NoUpdateMixin, RESTManager): +class HookManager(NoUpdateMixin[Hook]): _path = "/hooks" _obj_cls = Hook _create_attrs = RequiredOptional(required=("url",)) @@ -42,7 +42,7 @@ def test(self, trigger: str) -> None: self.manager.gitlab.http_post(path) -class ProjectHookManager(CRUDMixin, RESTManager): +class ProjectHookManager(CRUDMixin[ProjectHook]): _path = "/projects/{project_id}/hooks" _obj_cls = ProjectHook _from_parent_attrs = {"project_id": "id"} @@ -98,7 +98,7 @@ def test(self, trigger: str) -> None: self.manager.gitlab.http_post(path) -class GroupHookManager(CRUDMixin, RESTManager): +class GroupHookManager(CRUDMixin[GroupHook]): _path = "/groups/{group_id}/hooks" _obj_cls = GroupHook _from_parent_attrs = {"group_id": "id"} diff --git a/gitlab/v4/objects/integrations.py b/gitlab/v4/objects/integrations.py index 4f25ad7e6..bc431712f 100644 --- a/gitlab/v4/objects/integrations.py +++ b/gitlab/v4/objects/integrations.py @@ -6,7 +6,7 @@ from typing import List from gitlab import cli -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( DeleteMixin, GetMixin, @@ -29,7 +29,10 @@ class ProjectIntegration(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectIntegrationManager( - GetMixin, UpdateMixin, DeleteMixin, ListMixin, RESTManager + GetMixin[ProjectIntegration], + UpdateMixin[ProjectIntegration], + DeleteMixin[ProjectIntegration], + ListMixin[ProjectIntegration], ): _path = "/projects/{project_id}/integrations" _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/invitations.py b/gitlab/v4/objects/invitations.py index 516f4aaf8..789a77e34 100644 --- a/gitlab/v4/objects/invitations.py +++ b/gitlab/v4/objects/invitations.py @@ -1,6 +1,6 @@ -from typing import Any +from typing import Any, Dict, Optional -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject, TObjCls from gitlab.exceptions import GitlabInvitationError from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import ArrayAttribute, CommaSeparatedListAttribute, RequiredOptional @@ -13,9 +13,14 @@ ] -class InvitationMixin(CRUDMixin): - def create(self, *args: Any, **kwargs: Any) -> RESTObject: - invitation = super().create(*args, **kwargs) +class InvitationMixin(CRUDMixin[TObjCls]): + # pylint: disable=abstract-method + def create( + self, + data: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TObjCls: + invitation = super().create(data, **kwargs) if invitation.status == "error": raise GitlabInvitationError(invitation.message) @@ -27,7 +32,7 @@ class ProjectInvitation(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "email" -class ProjectInvitationManager(InvitationMixin, RESTManager): +class ProjectInvitationManager(InvitationMixin[ProjectInvitation]): _path = "/projects/{project_id}/invitations" _obj_cls = ProjectInvitation _from_parent_attrs = {"project_id": "id"} @@ -56,7 +61,7 @@ class GroupInvitation(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "email" -class GroupInvitationManager(InvitationMixin, RESTManager): +class GroupInvitationManager(InvitationMixin[GroupInvitation]): _path = "/groups/{group_id}/invitations" _obj_cls = GroupInvitation _from_parent_attrs = {"group_id": "id"} diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py index bcb5d412a..c99251bd0 100644 --- a/gitlab/v4/objects/issues.py +++ b/gitlab/v4/objects/issues.py @@ -5,7 +5,7 @@ from gitlab import cli, client from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -50,7 +50,7 @@ class Issue(RESTObject): _repr_attr = "title" -class IssueManager(RetrieveMixin, RESTManager): +class IssueManager(RetrieveMixin[Issue]): _path = "/issues" _obj_cls = Issue _list_filters = ( @@ -78,7 +78,7 @@ class GroupIssue(RESTObject): pass -class GroupIssueManager(ListMixin, RESTManager): +class GroupIssueManager(ListMixin[GroupIssue]): _path = "/groups/{group_id}/issues" _obj_cls = GroupIssue _from_parent_attrs = {"group_id": "id"} @@ -226,7 +226,7 @@ def closed_by( return result -class ProjectIssueManager(CRUDMixin, RESTManager): +class ProjectIssueManager(CRUDMixin[ProjectIssue]): _path = "/projects/{project_id}/issues" _obj_cls = ProjectIssue _from_parent_attrs = {"project_id": "id"} @@ -285,7 +285,11 @@ class ProjectIssueLink(ObjectDeleteMixin, RESTObject): _id_attr = "issue_link_id" -class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class ProjectIssueLinkManager( + ListMixin[ProjectIssueLink], + CreateMixin[ProjectIssueLink], + DeleteMixin[ProjectIssueLink], +): _path = "/projects/{project_id}/issues/{issue_iid}/links" _obj_cls = ProjectIssueLink _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -296,7 +300,7 @@ class ProjectIssueLinkManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): # type error def create( # type: ignore[override] self, data: Dict[str, Any], **kwargs: Any - ) -> Tuple[RESTObject, RESTObject]: + ) -> Tuple[ProjectIssue, ProjectIssue]: """Create a new object. Args: @@ -312,8 +316,6 @@ def create( # type: ignore[override] GitlabCreateError: If the server cannot perform the request """ self._create_attrs.validate_attrs(data=data) - if TYPE_CHECKING: - assert self.path is not None server_data = self.gitlab.http_post(self.path, post_data=data, **kwargs) if TYPE_CHECKING: assert isinstance(server_data, dict) diff --git a/gitlab/v4/objects/iterations.py b/gitlab/v4/objects/iterations.py index eac3f1f4e..fa0af1daa 100644 --- a/gitlab/v4/objects/iterations.py +++ b/gitlab/v4/objects/iterations.py @@ -1,5 +1,5 @@ from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ListMixin __all__ = [ @@ -13,7 +13,7 @@ class GroupIteration(RESTObject): _repr_attr = "title" -class GroupIterationManager(ListMixin, RESTManager): +class GroupIterationManager(ListMixin[GroupIteration]): _path = "/groups/{group_id}/iterations" _obj_cls = GroupIteration _from_parent_attrs = {"group_id": "id"} @@ -33,7 +33,7 @@ class GroupIterationManager(ListMixin, RESTManager): _types = {"in": types.ArrayAttribute} -class ProjectIterationManager(ListMixin, RESTManager): +class ProjectIterationManager(ListMixin[GroupIteration]): _path = "/projects/{project_id}/iterations" _obj_cls = GroupIteration _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/job_token_scope.py b/gitlab/v4/objects/job_token_scope.py index 64b579961..b6bd8f9a0 100644 --- a/gitlab/v4/objects/job_token_scope.py +++ b/gitlab/v4/objects/job_token_scope.py @@ -1,6 +1,6 @@ from typing import cast -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -27,7 +27,9 @@ class ProjectJobTokenScope(RefreshMixin, SaveMixin, RESTObject): groups_allowlist: "AllowlistGroupManager" -class ProjectJobTokenScopeManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ProjectJobTokenScopeManager( + GetWithoutIdMixin[ProjectJobTokenScope], UpdateMixin[ProjectJobTokenScope] +): _path = "/projects/{project_id}/job_token_scope" _obj_cls = ProjectJobTokenScope _from_parent_attrs = {"project_id": "id"} @@ -47,7 +49,11 @@ def get_id(self) -> int: return cast(int, self.id) -class AllowlistProjectManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class AllowlistProjectManager( + ListMixin[AllowlistProject], + CreateMixin[AllowlistProject], + DeleteMixin[AllowlistProject], +): _path = "/projects/{project_id}/job_token_scope/allowlist" _obj_cls = AllowlistProject _from_parent_attrs = {"project_id": "project_id"} @@ -67,7 +73,9 @@ def get_id(self) -> int: return cast(int, self.id) -class AllowlistGroupManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): +class AllowlistGroupManager( + ListMixin[AllowlistGroup], CreateMixin[AllowlistGroup], DeleteMixin[AllowlistGroup] +): _path = "/projects/{project_id}/job_token_scope/groups_allowlist" _obj_cls = AllowlistGroup _from_parent_attrs = {"project_id": "project_id"} diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py index 8f120035f..69d83aae1 100644 --- a/gitlab/v4/objects/jobs.py +++ b/gitlab/v4/objects/jobs.py @@ -15,7 +15,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import utils -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import RefreshMixin, RetrieveMixin from gitlab.types import ArrayAttribute @@ -353,7 +353,7 @@ def trace( ) -class ProjectJobManager(RetrieveMixin, RESTManager): +class ProjectJobManager(RetrieveMixin[ProjectJob]): _path = "/projects/{project_id}/jobs" _obj_cls = ProjectJob _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/keys.py b/gitlab/v4/objects/keys.py index caf8f602e..1bbc34095 100644 --- a/gitlab/v4/objects/keys.py +++ b/gitlab/v4/objects/keys.py @@ -1,6 +1,6 @@ -from typing import Any, cast, Optional, TYPE_CHECKING, Union +from typing import Any, Optional, TYPE_CHECKING, Union -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import GetMixin __all__ = [ @@ -13,7 +13,7 @@ class Key(RESTObject): pass -class KeyManager(GetMixin, RESTManager): +class KeyManager(GetMixin[Key]): _path = "/keys" _obj_cls = Key @@ -21,13 +21,11 @@ def get( self, id: Optional[Union[int, str]] = None, lazy: bool = False, **kwargs: Any ) -> Key: if id is not None: - return cast(Key, super().get(id, lazy=lazy, **kwargs)) + return super().get(id, lazy=lazy, **kwargs) if "fingerprint" not in kwargs: raise AttributeError("Missing attribute: id or fingerprint") - if TYPE_CHECKING: - assert self.path is not None server_data = self.gitlab.http_get(self.path, **kwargs) if TYPE_CHECKING: assert isinstance(server_data, dict) diff --git a/gitlab/v4/objects/labels.py b/gitlab/v4/objects/labels.py index dac1b1c75..488e2d05a 100644 --- a/gitlab/v4/objects/labels.py +++ b/gitlab/v4/objects/labels.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Optional from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -48,7 +48,10 @@ def save(self, **kwargs: Any) -> None: class GroupLabelManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + RetrieveMixin[GroupLabel], + CreateMixin[GroupLabel], + UpdateMixin[GroupLabel], + DeleteMixin[GroupLabel], ): _path = "/groups/{group_id}/labels" _obj_cls = GroupLabel @@ -109,7 +112,10 @@ def save(self, **kwargs: Any) -> None: class ProjectLabelManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + RetrieveMixin[ProjectLabel], + CreateMixin[ProjectLabel], + UpdateMixin[ProjectLabel], + DeleteMixin[ProjectLabel], ): _path = "/projects/{project_id}/labels" _obj_cls = ProjectLabel diff --git a/gitlab/v4/objects/ldap.py b/gitlab/v4/objects/ldap.py index 053cd1482..4f16e7010 100644 --- a/gitlab/v4/objects/ldap.py +++ b/gitlab/v4/objects/ldap.py @@ -13,7 +13,7 @@ class LDAPGroup(RESTObject): _id_attr = None -class LDAPGroupManager(RESTManager): +class LDAPGroupManager(RESTManager[LDAPGroup]): _path = "/ldap/groups" _obj_cls = LDAPGroup _list_filters = ("search", "provider") diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py index 19fa674ae..9551e33a7 100644 --- a/gitlab/v4/objects/members.py +++ b/gitlab/v4/objects/members.py @@ -1,5 +1,5 @@ from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CRUDMixin, DeleteMixin, @@ -30,7 +30,7 @@ class GroupMember(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "username" -class GroupMemberManager(CRUDMixin, RESTManager): +class GroupMemberManager(CRUDMixin[GroupMember]): _path = "/groups/{group_id}/members" _obj_cls = GroupMember _from_parent_attrs = {"group_id": "id"} @@ -54,7 +54,9 @@ class GroupBillableMember(ObjectDeleteMixin, RESTObject): memberships: "GroupBillableMemberMembershipManager" -class GroupBillableMemberManager(ListMixin, DeleteMixin, RESTManager): +class GroupBillableMemberManager( + ListMixin[GroupBillableMember], DeleteMixin[GroupBillableMember] +): _path = "/groups/{group_id}/billable_members" _obj_cls = GroupBillableMember _from_parent_attrs = {"group_id": "id"} @@ -65,7 +67,7 @@ class GroupBillableMemberMembership(RESTObject): _id_attr = "user_id" -class GroupBillableMemberMembershipManager(ListMixin, RESTManager): +class GroupBillableMemberMembershipManager(ListMixin[GroupBillableMemberMembership]): _path = "/groups/{group_id}/billable_members/{user_id}/memberships" _obj_cls = GroupBillableMemberMembership _from_parent_attrs = {"group_id": "group_id", "user_id": "id"} @@ -75,7 +77,7 @@ class GroupMemberAll(RESTObject): _repr_attr = "username" -class GroupMemberAllManager(RetrieveMixin, RESTManager): +class GroupMemberAllManager(RetrieveMixin[GroupMemberAll]): _path = "/groups/{group_id}/members/all" _obj_cls = GroupMemberAll _from_parent_attrs = {"group_id": "id"} @@ -85,7 +87,7 @@ class ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "username" -class ProjectMemberManager(CRUDMixin, RESTManager): +class ProjectMemberManager(CRUDMixin[ProjectMember]): _path = "/projects/{project_id}/members" _obj_cls = ProjectMember _from_parent_attrs = {"project_id": "id"} @@ -107,7 +109,7 @@ class ProjectMemberAll(RESTObject): _repr_attr = "username" -class ProjectMemberAllManager(RetrieveMixin, RESTManager): +class ProjectMemberAllManager(RetrieveMixin[ProjectMemberAll]): _path = "/projects/{project_id}/members/all" _obj_cls = ProjectMemberAll _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/merge_request_approvals.py b/gitlab/v4/objects/merge_request_approvals.py index 2ec230c42..d160eae6f 100644 --- a/gitlab/v4/objects/merge_request_approvals.py +++ b/gitlab/v4/objects/merge_request_approvals.py @@ -1,7 +1,7 @@ from typing import Any, List, Optional, TYPE_CHECKING from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -36,7 +36,11 @@ class GroupApprovalRule(SaveMixin, RESTObject): _repr_attr = "name" -class GroupApprovalRuleManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager): +class GroupApprovalRuleManager( + RetrieveMixin[GroupApprovalRule], + CreateMixin[GroupApprovalRule], + UpdateMixin[GroupApprovalRule], +): _path = "/groups/{group_id}/approval_rules" _obj_cls = GroupApprovalRule _from_parent_attrs = {"group_id": "id"} @@ -50,7 +54,9 @@ class ProjectApproval(SaveMixin, RESTObject): _id_attr = None -class ProjectApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ProjectApprovalManager( + GetWithoutIdMixin[ProjectApproval], UpdateMixin[ProjectApproval] +): _path = "/projects/{project_id}/approvals" _obj_cls = ProjectApproval _from_parent_attrs = {"project_id": "id"} @@ -72,7 +78,10 @@ class ProjectApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectApprovalRuleManager( - RetrieveMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + RetrieveMixin[ProjectApprovalRule], + CreateMixin[ProjectApprovalRule], + UpdateMixin[ProjectApprovalRule], + DeleteMixin[ProjectApprovalRule], ): _path = "/projects/{project_id}/approval_rules" _obj_cls = ProjectApprovalRule @@ -87,7 +96,10 @@ class ProjectMergeRequestApproval(SaveMixin, RESTObject): _id_attr = None -class ProjectMergeRequestApprovalManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ProjectMergeRequestApprovalManager( + GetWithoutIdMixin[ProjectMergeRequestApproval], + UpdateMixin[ProjectMergeRequestApproval], +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/approvals" _obj_cls = ProjectMergeRequestApproval _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -151,7 +163,9 @@ class ProjectMergeRequestApprovalRule(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class ProjectMergeRequestApprovalRuleManager(CRUDMixin, RESTManager): +class ProjectMergeRequestApprovalRuleManager( + CRUDMixin[ProjectMergeRequestApprovalRule] +): _path = "/projects/{project_id}/merge_requests/{merge_request_iid}/approval_rules" _obj_cls = ProjectMergeRequestApprovalRule _from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"} @@ -177,7 +191,9 @@ class ProjectMergeRequestApprovalState(RESTObject): pass -class ProjectMergeRequestApprovalStateManager(GetWithoutIdMixin, RESTManager): +class ProjectMergeRequestApprovalStateManager( + GetWithoutIdMixin[ProjectMergeRequestApprovalState] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/approval_state" _obj_cls = ProjectMergeRequestApprovalState _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index efc0624e4..0ff9efae8 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -12,7 +12,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject, RESTObjectList +from gitlab.base import RESTObject, RESTObjectList from gitlab.mixins import ( CRUDMixin, ListMixin, @@ -64,7 +64,7 @@ class MergeRequest(RESTObject): pass -class MergeRequestManager(ListMixin, RESTManager): +class MergeRequestManager(ListMixin[MergeRequest]): _path = "/merge_requests" _obj_cls = MergeRequest _list_filters = ( @@ -111,7 +111,7 @@ class GroupMergeRequest(RESTObject): pass -class GroupMergeRequestManager(ListMixin, RESTManager): +class GroupMergeRequestManager(ListMixin[GroupMergeRequest]): _path = "/groups/{group_id}/merge_requests" _obj_cls = GroupMergeRequest _from_parent_attrs = {"group_id": "id"} @@ -445,7 +445,7 @@ def merge( return server_data -class ProjectMergeRequestManager(CRUDMixin, RESTManager): +class ProjectMergeRequestManager(CRUDMixin[ProjectMergeRequest]): _path = "/projects/{project_id}/merge_requests" _obj_cls = ProjectMergeRequest _from_parent_attrs = {"project_id": "id"} @@ -532,7 +532,7 @@ class ProjectMergeRequestDiff(RESTObject): pass -class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager): +class ProjectMergeRequestDiffManager(RetrieveMixin[ProjectMergeRequestDiff]): _path = "/projects/{project_id}/merge_requests/{mr_iid}/versions" _obj_cls = ProjectMergeRequestDiff _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/merge_trains.py b/gitlab/v4/objects/merge_trains.py index 9f8e1dff0..fa1562abe 100644 --- a/gitlab/v4/objects/merge_trains.py +++ b/gitlab/v4/objects/merge_trains.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ListMixin __all__ = [ @@ -11,7 +11,7 @@ class ProjectMergeTrain(RESTObject): pass -class ProjectMergeTrainManager(ListMixin, RESTManager): +class ProjectMergeTrainManager(ListMixin[ProjectMergeTrain]): _path = "/projects/{project_id}/merge_trains" _obj_cls = ProjectMergeTrain _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/milestones.py b/gitlab/v4/objects/milestones.py index 602e26c0b..0f9a0cf37 100644 --- a/gitlab/v4/objects/milestones.py +++ b/gitlab/v4/objects/milestones.py @@ -3,7 +3,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject, RESTObjectList +from gitlab.base import RESTObject, RESTObjectList from gitlab.mixins import ( CRUDMixin, ObjectDeleteMixin, @@ -85,7 +85,7 @@ def merge_requests(self, **kwargs: Any) -> RESTObjectList: return RESTObjectList(manager, GroupMergeRequest, data_list) -class GroupMilestoneManager(CRUDMixin, RESTManager): +class GroupMilestoneManager(CRUDMixin[GroupMilestone]): _path = "/groups/{group_id}/milestones" _obj_cls = GroupMilestone _from_parent_attrs = {"group_id": "id"} @@ -159,7 +159,7 @@ def merge_requests(self, **kwargs: Any) -> RESTObjectList: return RESTObjectList(manager, ProjectMergeRequest, data_list) -class ProjectMilestoneManager(CRUDMixin, RESTManager): +class ProjectMilestoneManager(CRUDMixin[ProjectMilestone]): _path = "/projects/{project_id}/milestones" _obj_cls = ProjectMilestone _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/namespaces.py b/gitlab/v4/objects/namespaces.py index 8912c859b..66ef6e9e4 100644 --- a/gitlab/v4/objects/namespaces.py +++ b/gitlab/v4/objects/namespaces.py @@ -2,7 +2,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import RetrieveMixin from gitlab.utils import EncodedId @@ -16,7 +16,7 @@ class Namespace(RESTObject): pass -class NamespaceManager(RetrieveMixin, RESTManager): +class NamespaceManager(RetrieveMixin[Namespace]): _path = "/namespaces" _obj_cls = Namespace _list_filters = ("search",) diff --git a/gitlab/v4/objects/notes.py b/gitlab/v4/objects/notes.py index 2cc1f720a..f104c3f5d 100644 --- a/gitlab/v4/objects/notes.py +++ b/gitlab/v4/objects/notes.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -46,7 +46,7 @@ class GroupEpicNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: GroupEpicNoteAwardEmojiManager -class GroupEpicNoteManager(CRUDMixin, RESTManager): +class GroupEpicNoteManager(CRUDMixin[GroupEpicNote]): _path = "/groups/{group_id}/epics/{epic_id}/notes" _obj_cls = GroupEpicNote _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} @@ -59,7 +59,10 @@ class GroupEpicDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): class GroupEpicDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetMixin[GroupEpicDiscussionNote], + CreateMixin[GroupEpicDiscussionNote], + UpdateMixin[GroupEpicDiscussionNote], + DeleteMixin[GroupEpicDiscussionNote], ): _path = "/groups/{group_id}/epics/{epic_id}/discussions/{discussion_id}/notes" _obj_cls = GroupEpicDiscussionNote @@ -76,7 +79,7 @@ class ProjectNote(RESTObject): pass -class ProjectNoteManager(RetrieveMixin, RESTManager): +class ProjectNoteManager(RetrieveMixin[ProjectNote]): _path = "/projects/{project_id}/notes" _obj_cls = ProjectNote _from_parent_attrs = {"project_id": "id"} @@ -88,7 +91,10 @@ class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectCommitDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetMixin[ProjectCommitDiscussionNote], + CreateMixin[ProjectCommitDiscussionNote], + UpdateMixin[ProjectCommitDiscussionNote], + DeleteMixin[ProjectCommitDiscussionNote], ): _path = ( "/projects/{project_id}/repository/commits/{commit_id}/" @@ -110,7 +116,7 @@ class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectIssueNoteAwardEmojiManager -class ProjectIssueNoteManager(CRUDMixin, RESTManager): +class ProjectIssueNoteManager(CRUDMixin[ProjectIssueNote]): _path = "/projects/{project_id}/issues/{issue_iid}/notes" _obj_cls = ProjectIssueNote _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} @@ -123,7 +129,10 @@ class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectIssueDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetMixin[ProjectIssueDiscussionNote], + CreateMixin[ProjectIssueDiscussionNote], + UpdateMixin[ProjectIssueDiscussionNote], + DeleteMixin[ProjectIssueDiscussionNote], ): _path = ( "/projects/{project_id}/issues/{issue_iid}/discussions/{discussion_id}/notes" @@ -142,7 +151,7 @@ class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectMergeRequestNoteAwardEmojiManager -class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager): +class ProjectMergeRequestNoteManager(CRUDMixin[ProjectMergeRequestNote]): _path = "/projects/{project_id}/merge_requests/{mr_iid}/notes" _obj_cls = ProjectMergeRequestNote _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -155,7 +164,10 @@ class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject class ProjectMergeRequestDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetMixin[ProjectMergeRequestDiscussionNote], + CreateMixin[ProjectMergeRequestDiscussionNote], + UpdateMixin[ProjectMergeRequestDiscussionNote], + DeleteMixin[ProjectMergeRequestDiscussionNote], ): _path = ( "/projects/{project_id}/merge_requests/{mr_iid}/" @@ -175,7 +187,7 @@ class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectSnippetNoteAwardEmojiManager -class ProjectSnippetNoteManager(CRUDMixin, RESTManager): +class ProjectSnippetNoteManager(CRUDMixin[ProjectSnippetNote]): _path = "/projects/{project_id}/snippets/{snippet_id}/notes" _obj_cls = ProjectSnippetNote _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} @@ -188,7 +200,10 @@ class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectSnippetDiscussionNoteManager( - GetMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetMixin[ProjectSnippetDiscussionNote], + CreateMixin[ProjectSnippetDiscussionNote], + UpdateMixin[ProjectSnippetDiscussionNote], + DeleteMixin[ProjectSnippetDiscussionNote], ): _path = ( "/projects/{project_id}/snippets/{snippet_id}/" diff --git a/gitlab/v4/objects/notification_settings.py b/gitlab/v4/objects/notification_settings.py index 998a92929..bd341a957 100644 --- a/gitlab/v4/objects/notification_settings.py +++ b/gitlab/v4/objects/notification_settings.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -16,7 +16,9 @@ class NotificationSettings(SaveMixin, RESTObject): _id_attr = None -class NotificationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class NotificationSettingsManager( + GetWithoutIdMixin[NotificationSettings], UpdateMixin[NotificationSettings] +): _path = "/notification_settings" _obj_cls = NotificationSettings diff --git a/gitlab/v4/objects/package_protection_rules.py b/gitlab/v4/objects/package_protection_rules.py index b86343898..e7c0a0cae 100644 --- a/gitlab/v4/objects/package_protection_rules.py +++ b/gitlab/v4/objects/package_protection_rules.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -21,7 +21,10 @@ class ProjectPackageProtectionRule(ObjectDeleteMixin, SaveMixin, RESTObject): class ProjectPackageProtectionRuleManager( - ListMixin, CreateMixin, DeleteMixin, UpdateMixin, RESTManager + ListMixin[ProjectPackageProtectionRule], + CreateMixin[ProjectPackageProtectionRule], + DeleteMixin[ProjectPackageProtectionRule], + UpdateMixin[ProjectPackageProtectionRule], ): _path = "/projects/{project_id}/packages/protection/rules" _obj_cls = ProjectPackageProtectionRule diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py index 05677780f..fb2d9f797 100644 --- a/gitlab/v4/objects/packages.py +++ b/gitlab/v4/objects/packages.py @@ -43,7 +43,7 @@ class GenericPackage(RESTObject): _id_attr = "package_name" -class GenericPackageManager(RESTManager): +class GenericPackageManager(RESTManager[GenericPackage]): _path = "/projects/{project_id}/packages/generic" _obj_cls = GenericPackage _from_parent_attrs = {"project_id": "id"} @@ -218,7 +218,7 @@ class GroupPackage(RESTObject): pass -class GroupPackageManager(ListMixin, RESTManager): +class GroupPackageManager(ListMixin[GroupPackage]): _path = "/groups/{group_id}/packages" _obj_cls = GroupPackage _from_parent_attrs = {"group_id": "id"} @@ -236,7 +236,9 @@ class ProjectPackage(ObjectDeleteMixin, RESTObject): pipelines: "ProjectPackagePipelineManager" -class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager): +class ProjectPackageManager( + ListMixin[ProjectPackage], GetMixin[ProjectPackage], DeleteMixin[ProjectPackage] +): _path = "/projects/{project_id}/packages" _obj_cls = ProjectPackage _from_parent_attrs = {"project_id": "id"} @@ -252,7 +254,9 @@ class ProjectPackageFile(ObjectDeleteMixin, RESTObject): pass -class ProjectPackageFileManager(DeleteMixin, ListMixin, RESTManager): +class ProjectPackageFileManager( + DeleteMixin[ProjectPackageFile], ListMixin[ProjectPackageFile] +): _path = "/projects/{project_id}/packages/{package_id}/package_files" _obj_cls = ProjectPackageFile _from_parent_attrs = {"project_id": "project_id", "package_id": "id"} @@ -262,7 +266,7 @@ class ProjectPackagePipeline(RESTObject): pass -class ProjectPackagePipelineManager(ListMixin, RESTManager): +class ProjectPackagePipelineManager(ListMixin[ProjectPackagePipeline]): _path = "/projects/{project_id}/packages/{package_id}/pipelines" _obj_cls = ProjectPackagePipeline _from_parent_attrs = {"project_id": "project_id", "package_id": "id"} diff --git a/gitlab/v4/objects/pages.py b/gitlab/v4/objects/pages.py index 6891b2a03..ae0b1f43a 100644 --- a/gitlab/v4/objects/pages.py +++ b/gitlab/v4/objects/pages.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CRUDMixin, DeleteMixin, @@ -26,7 +26,7 @@ class PagesDomain(RESTObject): _id_attr = "domain" -class PagesDomainManager(ListMixin, RESTManager): +class PagesDomainManager(ListMixin[PagesDomain]): _path = "/pages/domains" _obj_cls = PagesDomain @@ -35,7 +35,7 @@ class ProjectPagesDomain(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "domain" -class ProjectPagesDomainManager(CRUDMixin, RESTManager): +class ProjectPagesDomainManager(CRUDMixin[ProjectPagesDomain]): _path = "/projects/{project_id}/pages/domains" _obj_cls = ProjectPagesDomain _from_parent_attrs = {"project_id": "id"} @@ -49,7 +49,11 @@ class ProjectPages(ObjectDeleteMixin, RefreshMixin, RESTObject): _id_attr = None -class ProjectPagesManager(DeleteMixin, UpdateMixin, GetWithoutIdMixin, RESTManager): +class ProjectPagesManager( + DeleteMixin[ProjectPages], + UpdateMixin[ProjectPages], + GetWithoutIdMixin[ProjectPages], +): _path = "/projects/{project_id}/pages" _obj_cls = ProjectPages _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/personal_access_tokens.py b/gitlab/v4/objects/personal_access_tokens.py index 12161a049..ec667499f 100644 --- a/gitlab/v4/objects/personal_access_tokens.py +++ b/gitlab/v4/objects/personal_access_tokens.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -21,7 +21,11 @@ class PersonalAccessToken(ObjectDeleteMixin, ObjectRotateMixin, RESTObject): pass -class PersonalAccessTokenManager(DeleteMixin, RetrieveMixin, RotateMixin, RESTManager): +class PersonalAccessTokenManager( + DeleteMixin[PersonalAccessToken], + RetrieveMixin[PersonalAccessToken], + RotateMixin[PersonalAccessToken], +): _path = "/personal_access_tokens" _obj_cls = PersonalAccessToken _list_filters = ("user_id",) @@ -31,7 +35,7 @@ class UserPersonalAccessToken(RESTObject): pass -class UserPersonalAccessTokenManager(CreateMixin, RESTManager): +class UserPersonalAccessTokenManager(CreateMixin[UserPersonalAccessToken]): _path = "/users/{user_id}/personal_access_tokens" _obj_cls = UserPersonalAccessToken _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py index 303629ada..2645b7064 100644 --- a/gitlab/v4/objects/pipelines.py +++ b/gitlab/v4/objects/pipelines.py @@ -1,10 +1,10 @@ -from typing import Any, cast, Dict, Optional, TYPE_CHECKING, Union +from typing import Any, Dict, Optional, TYPE_CHECKING, Union import requests from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -47,7 +47,9 @@ class ProjectMergeRequestPipeline(RESTObject): pass -class ProjectMergeRequestPipelineManager(CreateMixin, ListMixin, RESTManager): +class ProjectMergeRequestPipelineManager( + CreateMixin[ProjectMergeRequestPipeline], ListMixin[ProjectMergeRequestPipeline] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/pipelines" _obj_cls = ProjectMergeRequestPipeline _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} @@ -91,7 +93,11 @@ def retry(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: return self.manager.gitlab.http_post(path, **kwargs) -class ProjectPipelineManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class ProjectPipelineManager( + RetrieveMixin[ProjectPipeline], + CreateMixin[ProjectPipeline], + DeleteMixin[ProjectPipeline], +): _path = "/projects/{project_id}/pipelines" _obj_cls = ProjectPipeline _from_parent_attrs = {"project_id": "id"} @@ -127,12 +133,8 @@ def create( A new instance of the managed object class build with the data sent by the server """ - if TYPE_CHECKING: - assert self.path is not None path = self.path[:-1] # drop the 's' - return cast( - ProjectPipeline, CreateMixin.create(self, data, path=path, **kwargs) - ) + return super().create(data, path=path, **kwargs) def latest(self, ref: Optional[str] = None, lazy: bool = False) -> ProjectPipeline: """Get the latest pipeline for the most recent commit @@ -147,9 +149,6 @@ def latest(self, ref: Optional[str] = None, lazy: bool = False) -> ProjectPipeli data = {} if ref: data = {"ref": ref} - if TYPE_CHECKING: - assert self._obj_cls is not None - assert self.path is not None server_data = self.gitlab.http_get(self.path + "/latest", query_data=data) if TYPE_CHECKING: assert not isinstance(server_data, requests.Response) @@ -160,7 +159,7 @@ class ProjectPipelineJob(RESTObject): pass -class ProjectPipelineJobManager(ListMixin, RESTManager): +class ProjectPipelineJobManager(ListMixin[ProjectPipelineJob]): _path = "/projects/{project_id}/pipelines/{pipeline_id}/jobs" _obj_cls = ProjectPipelineJob _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} @@ -172,7 +171,7 @@ class ProjectPipelineBridge(RESTObject): pass -class ProjectPipelineBridgeManager(ListMixin, RESTManager): +class ProjectPipelineBridgeManager(ListMixin[ProjectPipelineBridge]): _path = "/projects/{project_id}/pipelines/{pipeline_id}/bridges" _obj_cls = ProjectPipelineBridge _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} @@ -183,7 +182,7 @@ class ProjectPipelineVariable(RESTObject): _id_attr = "key" -class ProjectPipelineVariableManager(ListMixin, RESTManager): +class ProjectPipelineVariableManager(ListMixin[ProjectPipelineVariable]): _path = "/projects/{project_id}/pipelines/{pipeline_id}/variables" _obj_cls = ProjectPipelineVariable _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} @@ -194,7 +193,9 @@ class ProjectPipelineScheduleVariable(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectPipelineScheduleVariableManager( - CreateMixin, UpdateMixin, DeleteMixin, RESTManager + CreateMixin[ProjectPipelineScheduleVariable], + UpdateMixin[ProjectPipelineScheduleVariable], + DeleteMixin[ProjectPipelineScheduleVariable], ): _path = "/projects/{project_id}/pipeline_schedules/{pipeline_schedule_id}/variables" _obj_cls = ProjectPipelineScheduleVariable @@ -207,7 +208,9 @@ class ProjectPipelineSchedulePipeline(RESTObject): pass -class ProjectPipelineSchedulePipelineManager(ListMixin, RESTManager): +class ProjectPipelineSchedulePipelineManager( + ListMixin[ProjectPipelineSchedulePipeline] +): _path = "/projects/{project_id}/pipeline_schedules/{pipeline_schedule_id}/pipelines" _obj_cls = ProjectPipelineSchedulePipeline _from_parent_attrs = {"project_id": "project_id", "pipeline_schedule_id": "id"} @@ -256,7 +259,7 @@ def play(self, **kwargs: Any) -> Dict[str, Any]: return server_data -class ProjectPipelineScheduleManager(CRUDMixin, RESTManager): +class ProjectPipelineScheduleManager(CRUDMixin[ProjectPipelineSchedule]): _path = "/projects/{project_id}/pipeline_schedules" _obj_cls = ProjectPipelineSchedule _from_parent_attrs = {"project_id": "id"} @@ -272,7 +275,7 @@ class ProjectPipelineTestReport(RESTObject): _id_attr = None -class ProjectPipelineTestReportManager(GetWithoutIdMixin, RESTManager): +class ProjectPipelineTestReportManager(GetWithoutIdMixin[ProjectPipelineTestReport]): _path = "/projects/{project_id}/pipelines/{pipeline_id}/test_report" _obj_cls = ProjectPipelineTestReport _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} @@ -282,7 +285,9 @@ class ProjectPipelineTestReportSummary(RESTObject): _id_attr = None -class ProjectPipelineTestReportSummaryManager(GetWithoutIdMixin, RESTManager): +class ProjectPipelineTestReportSummaryManager( + GetWithoutIdMixin[ProjectPipelineTestReportSummary] +): _path = "/projects/{project_id}/pipelines/{pipeline_id}/test_report_summary" _obj_cls = ProjectPipelineTestReportSummary _from_parent_attrs = {"project_id": "project_id", "pipeline_id": "id"} diff --git a/gitlab/v4/objects/project_access_tokens.py b/gitlab/v4/objects/project_access_tokens.py index ea01a29da..f16290890 100644 --- a/gitlab/v4/objects/project_access_tokens.py +++ b/gitlab/v4/objects/project_access_tokens.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -20,7 +20,10 @@ class ProjectAccessToken(ObjectDeleteMixin, ObjectRotateMixin, RESTObject): class ProjectAccessTokenManager( - CreateMixin, DeleteMixin, RetrieveMixin, RotateMixin, RESTManager + CreateMixin[ProjectAccessToken], + DeleteMixin[ProjectAccessToken], + RetrieveMixin[ProjectAccessToken], + RotateMixin[ProjectAccessToken], ): _path = "/projects/{project_id}/access_tokens" _obj_cls = ProjectAccessToken diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index d585eb9d5..ea211cba5 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -7,7 +7,6 @@ from typing import ( Any, Callable, - cast, Dict, Iterator, List, @@ -23,7 +22,7 @@ from gitlab import cli, client from gitlab import exceptions as exc from gitlab import types, utils -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -141,7 +140,7 @@ class GroupProject(RESTObject): pass -class GroupProjectManager(ListMixin, RESTManager): +class GroupProjectManager(ListMixin[GroupProject]): _path = "/groups/{group_id}/projects" _obj_cls = GroupProject _from_parent_attrs = {"group_id": "id"} @@ -168,7 +167,7 @@ class ProjectGroup(RESTObject): pass -class ProjectGroupManager(ListMixin, RESTManager): +class ProjectGroupManager(ListMixin[ProjectGroup]): _path = "/projects/{project_id}/groups" _obj_cls = ProjectGroup _from_parent_attrs = {"project_id": "id"} @@ -668,7 +667,7 @@ def transfer(self, to_namespace: Union[int, str], **kwargs: Any) -> None: ) -class ProjectManager(CRUDMixin, RESTManager): +class ProjectManager(CRUDMixin[Project]): _path = "/projects" _obj_cls = Project # Please keep these _create_attrs in same order as they are at: @@ -1193,7 +1192,7 @@ class ProjectFork(RESTObject): pass -class ProjectForkManager(CreateMixin, ListMixin, RESTManager): +class ProjectForkManager(CreateMixin[ProjectFork], ListMixin[ProjectFork]): _path = "/projects/{project_id}/forks" _obj_cls = ProjectFork _from_parent_attrs = {"project_id": "id"} @@ -1232,10 +1231,8 @@ def create( A new instance of the managed object class build with the data sent by the server """ - if TYPE_CHECKING: - assert self.path is not None path = self.path[:-1] # drop the 's' - return cast(ProjectFork, CreateMixin.create(self, data, path=path, **kwargs)) + return super().create(data, path=path, **kwargs) class ProjectRemoteMirror(ObjectDeleteMixin, SaveMixin, RESTObject): @@ -1243,7 +1240,10 @@ class ProjectRemoteMirror(ObjectDeleteMixin, SaveMixin, RESTObject): class ProjectRemoteMirrorManager( - ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + ListMixin[ProjectRemoteMirror], + CreateMixin[ProjectRemoteMirror], + UpdateMixin[ProjectRemoteMirror], + DeleteMixin[ProjectRemoteMirror], ): _path = "/projects/{project_id}/remote_mirrors" _obj_cls = ProjectRemoteMirror @@ -1258,7 +1258,9 @@ class ProjectPullMirror(SaveMixin, RESTObject): _id_attr = None -class ProjectPullMirrorManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ProjectPullMirrorManager( + GetWithoutIdMixin[ProjectPullMirror], UpdateMixin[ProjectPullMirror] +): _path = "/projects/{project_id}/mirror/pull" _obj_cls = ProjectPullMirror _from_parent_attrs = {"project_id": "id"} @@ -1285,8 +1287,6 @@ def create(self, data: Dict[str, Any], **kwargs: Any) -> ProjectPullMirror: assert data is not None self._create_attrs.validate_attrs(data=data) - if TYPE_CHECKING: - assert self.path is not None server_data = self.gitlab.http_put(self.path, post_data=data, **kwargs) if TYPE_CHECKING: @@ -1305,8 +1305,6 @@ def start(self, **kwargs: Any) -> None: GitlabAuthenticationError: If authentication is not correct GitlabCreateError: If the server failed to perform the request """ - if TYPE_CHECKING: - assert self.path is not None self.gitlab.http_post(self.path, **kwargs) @@ -1314,7 +1312,7 @@ class ProjectStorage(RefreshMixin, RESTObject): pass -class ProjectStorageManager(GetWithoutIdMixin, RESTManager): +class ProjectStorageManager(GetWithoutIdMixin[ProjectStorage]): _path = "/projects/{project_id}/storage" _obj_cls = ProjectStorage _from_parent_attrs = {"project_id": "id"} @@ -1324,7 +1322,7 @@ class SharedProject(RESTObject): pass -class SharedProjectManager(ListMixin, RESTManager): +class SharedProjectManager(ListMixin[SharedProject]): _path = "/groups/{group_id}/projects/shared" _obj_cls = SharedProject _from_parent_attrs = {"group_id": "id"} diff --git a/gitlab/v4/objects/push_rules.py b/gitlab/v4/objects/push_rules.py index 0b5c5a5b9..42321cd9d 100644 --- a/gitlab/v4/objects/push_rules.py +++ b/gitlab/v4/objects/push_rules.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -22,7 +22,10 @@ class ProjectPushRules(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectPushRulesManager( - GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetWithoutIdMixin[ProjectPushRules], + CreateMixin[ProjectPushRules], + UpdateMixin[ProjectPushRules], + DeleteMixin[ProjectPushRules], ): _path = "/projects/{project_id}/push_rule" _obj_cls = ProjectPushRules @@ -64,7 +67,10 @@ class GroupPushRules(SaveMixin, ObjectDeleteMixin, RESTObject): class GroupPushRulesManager( - GetWithoutIdMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + GetWithoutIdMixin[GroupPushRules], + CreateMixin[GroupPushRules], + UpdateMixin[GroupPushRules], + DeleteMixin[GroupPushRules], ): _path = "/groups/{group_id}/push_rule" _obj_cls = GroupPushRules diff --git a/gitlab/v4/objects/registry_protection_repository_rules.py b/gitlab/v4/objects/registry_protection_repository_rules.py index a73629e39..c8cda5902 100644 --- a/gitlab/v4/objects/registry_protection_repository_rules.py +++ b/gitlab/v4/objects/registry_protection_repository_rules.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, ListMixin, SaveMixin, UpdateMethod, UpdateMixin from gitlab.types import RequiredOptional @@ -13,7 +13,9 @@ class ProjectRegistryRepositoryProtectionRule(SaveMixin, RESTObject): class ProjectRegistryRepositoryProtectionRuleManager( - ListMixin, CreateMixin, UpdateMixin, RESTManager + ListMixin[ProjectRegistryRepositoryProtectionRule], + CreateMixin[ProjectRegistryRepositoryProtectionRule], + UpdateMixin[ProjectRegistryRepositoryProtectionRule], ): _path = "/projects/{project_id}/registry/protection/repository/rules" _obj_cls = ProjectRegistryRepositoryProtectionRule diff --git a/gitlab/v4/objects/registry_protection_rules.py b/gitlab/v4/objects/registry_protection_rules.py index 0c1d0214b..b60a8f2e5 100644 --- a/gitlab/v4/objects/registry_protection_rules.py +++ b/gitlab/v4/objects/registry_protection_rules.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, ListMixin, SaveMixin, UpdateMethod, UpdateMixin from gitlab.types import RequiredOptional @@ -13,7 +13,9 @@ class ProjectRegistryProtectionRule(SaveMixin, RESTObject): class ProjectRegistryProtectionRuleManager( - ListMixin, CreateMixin, UpdateMixin, RESTManager + ListMixin[ProjectRegistryProtectionRule], + CreateMixin[ProjectRegistryProtectionRule], + UpdateMixin[ProjectRegistryProtectionRule], ): _path = "/projects/{project_id}/registry/protection/rules" _obj_cls = ProjectRegistryProtectionRule diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py index 9dc9439dd..30794e2aa 100644 --- a/gitlab/v4/objects/releases.py +++ b/gitlab/v4/objects/releases.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import ArrayAttribute, RequiredOptional @@ -16,7 +16,7 @@ class ProjectRelease(SaveMixin, RESTObject): links: "ProjectReleaseLinkManager" -class ProjectReleaseManager(CRUDMixin, RESTManager): +class ProjectReleaseManager(CRUDMixin[ProjectRelease]): _path = "/projects/{project_id}/releases" _obj_cls = ProjectRelease _from_parent_attrs = {"project_id": "id"} @@ -38,7 +38,7 @@ class ProjectReleaseLink(ObjectDeleteMixin, SaveMixin, RESTObject): pass -class ProjectReleaseLinkManager(CRUDMixin, RESTManager): +class ProjectReleaseLinkManager(CRUDMixin[ProjectReleaseLink]): _path = "/projects/{project_id}/releases/{tag_name}/assets/links" _obj_cls = ProjectReleaseLink _from_parent_attrs = {"project_id": "project_id", "tag_name": "tag_name"} diff --git a/gitlab/v4/objects/resource_groups.py b/gitlab/v4/objects/resource_groups.py index ec5cfcf5d..ef8e444c9 100644 --- a/gitlab/v4/objects/resource_groups.py +++ b/gitlab/v4/objects/resource_groups.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ListMixin, RetrieveMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -16,7 +16,9 @@ class ProjectResourceGroup(SaveMixin, RESTObject): upcoming_jobs: "ProjectResourceGroupUpcomingJobManager" -class ProjectResourceGroupManager(RetrieveMixin, UpdateMixin, RESTManager): +class ProjectResourceGroupManager( + RetrieveMixin[ProjectResourceGroup], UpdateMixin[ProjectResourceGroup] +): _path = "/projects/{project_id}/resource_groups" _obj_cls = ProjectResourceGroup _from_parent_attrs = {"project_id": "id"} @@ -32,7 +34,9 @@ class ProjectResourceGroupUpcomingJob(RESTObject): pass -class ProjectResourceGroupUpcomingJobManager(ListMixin, RESTManager): +class ProjectResourceGroupUpcomingJobManager( + ListMixin[ProjectResourceGroupUpcomingJob] +): _path = "/projects/{project_id}/resource_groups/{resource_group_key}/upcoming_jobs" _obj_cls = ProjectResourceGroupUpcomingJob _from_parent_attrs = {"project_id": "project_id", "resource_group_key": "key"} diff --git a/gitlab/v4/objects/reviewers.py b/gitlab/v4/objects/reviewers.py index 9e21736cd..95fcd143d 100644 --- a/gitlab/v4/objects/reviewers.py +++ b/gitlab/v4/objects/reviewers.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ListMixin __all__ = [ @@ -11,7 +11,9 @@ class ProjectMergeRequestReviewerDetail(RESTObject): pass -class ProjectMergeRequestReviewerDetailManager(ListMixin, RESTManager): +class ProjectMergeRequestReviewerDetailManager( + ListMixin[ProjectMergeRequestReviewerDetail] +): _path = "/projects/{project_id}/merge_requests/{mr_iid}/reviewers" _obj_cls = ProjectMergeRequestReviewerDetail _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py index 9d0eb3a83..faa82ddbd 100644 --- a/gitlab/v4/objects/runners.py +++ b/gitlab/v4/objects/runners.py @@ -3,7 +3,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -32,7 +32,7 @@ class RunnerJob(RESTObject): pass -class RunnerJobManager(ListMixin, RESTManager): +class RunnerJobManager(ListMixin[RunnerJob]): _path = "/runners/{runner_id}/jobs" _obj_cls = RunnerJob _from_parent_attrs = {"runner_id": "id"} @@ -44,7 +44,7 @@ class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): _repr_attr = "description" -class RunnerManager(CRUDMixin, RESTManager): +class RunnerManager(CRUDMixin[Runner]): _path = "/runners" _obj_cls = Runner _create_attrs = RequiredOptional( @@ -125,7 +125,7 @@ class RunnerAll(RESTObject): _repr_attr = "description" -class RunnerAllManager(ListMixin, RESTManager): +class RunnerAllManager(ListMixin[RunnerAll]): _path = "/runners/all" _obj_cls = RunnerAll _list_filters = ("scope", "type", "status", "paused", "tag_list") @@ -136,7 +136,7 @@ class GroupRunner(RESTObject): pass -class GroupRunnerManager(ListMixin, RESTManager): +class GroupRunnerManager(ListMixin[GroupRunner]): _path = "/groups/{group_id}/runners" _obj_cls = GroupRunner _from_parent_attrs = {"group_id": "id"} @@ -149,7 +149,9 @@ class ProjectRunner(ObjectDeleteMixin, RESTObject): pass -class ProjectRunnerManager(CreateMixin, DeleteMixin, ListMixin, RESTManager): +class ProjectRunnerManager( + CreateMixin[ProjectRunner], DeleteMixin[ProjectRunner], ListMixin[ProjectRunner] +): _path = "/projects/{project_id}/runners" _obj_cls = ProjectRunner _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/secure_files.py b/gitlab/v4/objects/secure_files.py index 0cd8454f8..6ced2c306 100644 --- a/gitlab/v4/objects/secure_files.py +++ b/gitlab/v4/objects/secure_files.py @@ -19,7 +19,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import utils -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin from gitlab.types import FileAttribute, RequiredOptional @@ -101,7 +101,7 @@ def download( ) -class ProjectSecureFileManager(NoUpdateMixin, RESTManager): +class ProjectSecureFileManager(NoUpdateMixin[ProjectSecureFile]): _path = "/projects/{project_id}/secure_files" _obj_cls = ProjectSecureFile _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/service_accounts.py b/gitlab/v4/objects/service_accounts.py index e73dd7bd4..7857814c3 100644 --- a/gitlab/v4/objects/service_accounts.py +++ b/gitlab/v4/objects/service_accounts.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin from gitlab.types import RequiredOptional @@ -9,7 +9,11 @@ class GroupServiceAccount(ObjectDeleteMixin, RESTObject): pass -class GroupServiceAccountManager(CreateMixin, DeleteMixin, ListMixin, RESTManager): +class GroupServiceAccountManager( + CreateMixin[GroupServiceAccount], + DeleteMixin[GroupServiceAccount], + ListMixin[GroupServiceAccount], +): _path = "/groups/{group_id}/service_accounts" _obj_cls = GroupServiceAccount _from_parent_attrs = {"group_id": "id"} diff --git a/gitlab/v4/objects/settings.py b/gitlab/v4/objects/settings.py index 328f91073..63777420e 100644 --- a/gitlab/v4/objects/settings.py +++ b/gitlab/v4/objects/settings.py @@ -2,7 +2,7 @@ from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import GetWithoutIdMixin, SaveMixin, UpdateMixin from gitlab.types import RequiredOptional @@ -16,7 +16,9 @@ class ApplicationSettings(SaveMixin, RESTObject): _id_attr = None -class ApplicationSettingsManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class ApplicationSettingsManager( + GetWithoutIdMixin[ApplicationSettings], UpdateMixin[ApplicationSettings] +): _path = "/application/settings" _obj_cls = ApplicationSettings _update_attrs = RequiredOptional( diff --git a/gitlab/v4/objects/sidekiq.py b/gitlab/v4/objects/sidekiq.py index 5a11d633e..8f89971f8 100644 --- a/gitlab/v4/objects/sidekiq.py +++ b/gitlab/v4/objects/sidekiq.py @@ -4,20 +4,23 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager +from gitlab.base import RESTManager, RESTObject __all__ = [ "SidekiqManager", ] -class SidekiqManager(RESTManager): +class SidekiqManager(RESTManager[RESTObject]): """Manager for the Sidekiq methods. This manager doesn't actually manage objects but provides helper function for the sidekiq metrics API. """ + _path = "/sidekiq" + _obj_cls = RESTObject + @cli.register_custom_action(cls_names="SidekiqManager") @exc.on_http_error(exc.GitlabGetError) def queue_metrics(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: @@ -33,7 +36,7 @@ def queue_metrics(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Respons Returns: Information about the Sidekiq queues """ - return self.gitlab.http_get("/sidekiq/queue_metrics", **kwargs) + return self.gitlab.http_get(f"{self.path}/queue_metrics", **kwargs) @cli.register_custom_action(cls_names="SidekiqManager") @exc.on_http_error(exc.GitlabGetError) @@ -52,7 +55,7 @@ def process_metrics( Returns: Information about the register Sidekiq worker """ - return self.gitlab.http_get("/sidekiq/process_metrics", **kwargs) + return self.gitlab.http_get(f"{self.path}/process_metrics", **kwargs) @cli.register_custom_action(cls_names="SidekiqManager") @exc.on_http_error(exc.GitlabGetError) @@ -69,7 +72,7 @@ def job_stats(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: Returns: Statistics about the Sidekiq jobs performed """ - return self.gitlab.http_get("/sidekiq/job_stats", **kwargs) + return self.gitlab.http_get(f"{self.path}/job_stats", **kwargs) @cli.register_custom_action(cls_names="SidekiqManager") @exc.on_http_error(exc.GitlabGetError) @@ -88,4 +91,4 @@ def compound_metrics( Returns: All available Sidekiq metrics and statistics """ - return self.gitlab.http_get("/sidekiq/compound_metrics", **kwargs) + return self.gitlab.http_get(f"{self.path}/compound_metrics", **kwargs) diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py index 48eef463a..563f43f95 100644 --- a/gitlab/v4/objects/snippets.py +++ b/gitlab/v4/objects/snippets.py @@ -15,7 +15,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import utils -from gitlab.base import RESTManager, RESTObject, RESTObjectList +from gitlab.base import RESTObject, RESTObjectList from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin, UserAgentDetailMixin from gitlab.types import RequiredOptional @@ -109,7 +109,7 @@ def content( ) -class SnippetManager(CRUDMixin, RESTManager): +class SnippetManager(CRUDMixin[Snippet]): _path = "/snippets" _obj_cls = Snippet _create_attrs = RequiredOptional( @@ -133,7 +133,7 @@ class SnippetManager(CRUDMixin, RESTManager): ) @cli.register_custom_action(cls_names="SnippetManager") - def list_public(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: + def list_public(self, **kwargs: Any) -> Union[RESTObjectList, List[Snippet]]: """List all public snippets. Args: @@ -153,7 +153,7 @@ def list_public(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: return self.list(path="/snippets/public", **kwargs) @cli.register_custom_action(cls_names="SnippetManager") - def list_all(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: + def list_all(self, **kwargs: Any) -> Union[RESTObjectList, List[Snippet]]: """List all snippets. Args: @@ -172,7 +172,7 @@ def list_all(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: """ return self.list(path="/snippets/all", **kwargs) - def public(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: + def public(self, **kwargs: Any) -> Union[RESTObjectList, List[Snippet]]: """List all public snippets. Args: @@ -282,7 +282,7 @@ def content( ) -class ProjectSnippetManager(CRUDMixin, RESTManager): +class ProjectSnippetManager(CRUDMixin[ProjectSnippet]): _path = "/projects/{project_id}/snippets" _obj_cls = ProjectSnippet _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/statistics.py b/gitlab/v4/objects/statistics.py index ce8f96c3b..4a3033f9b 100644 --- a/gitlab/v4/objects/statistics.py +++ b/gitlab/v4/objects/statistics.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import GetWithoutIdMixin, RefreshMixin from gitlab.types import ArrayAttribute @@ -20,7 +20,9 @@ class ProjectAdditionalStatistics(RefreshMixin, RESTObject): _id_attr = None -class ProjectAdditionalStatisticsManager(GetWithoutIdMixin, RESTManager): +class ProjectAdditionalStatisticsManager( + GetWithoutIdMixin[ProjectAdditionalStatistics] +): _path = "/projects/{project_id}/statistics" _obj_cls = ProjectAdditionalStatistics _from_parent_attrs = {"project_id": "id"} @@ -30,7 +32,7 @@ class IssuesStatistics(RefreshMixin, RESTObject): _id_attr = None -class IssuesStatisticsManager(GetWithoutIdMixin, RESTManager): +class IssuesStatisticsManager(GetWithoutIdMixin[IssuesStatistics]): _path = "/issues_statistics" _obj_cls = IssuesStatistics _list_filters = ("iids",) @@ -41,7 +43,7 @@ class GroupIssuesStatistics(RefreshMixin, RESTObject): _id_attr = None -class GroupIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): +class GroupIssuesStatisticsManager(GetWithoutIdMixin[GroupIssuesStatistics]): _path = "/groups/{group_id}/issues_statistics" _obj_cls = GroupIssuesStatistics _from_parent_attrs = {"group_id": "id"} @@ -53,7 +55,7 @@ class ProjectIssuesStatistics(RefreshMixin, RESTObject): _id_attr = None -class ProjectIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): +class ProjectIssuesStatisticsManager(GetWithoutIdMixin[ProjectIssuesStatistics]): _path = "/projects/{project_id}/issues_statistics" _obj_cls = ProjectIssuesStatistics _from_parent_attrs = {"project_id": "id"} @@ -65,6 +67,6 @@ class ApplicationStatistics(RESTObject): _id_attr = None -class ApplicationStatisticsManager(GetWithoutIdMixin, RESTManager): +class ApplicationStatisticsManager(GetWithoutIdMixin[ApplicationStatistics]): _path = "/application/statistics" _obj_cls = ApplicationStatistics diff --git a/gitlab/v4/objects/status_checks.py b/gitlab/v4/objects/status_checks.py index 045b57260..e54b7444e 100644 --- a/gitlab/v4/objects/status_checks.py +++ b/gitlab/v4/objects/status_checks.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import ( CreateMixin, DeleteMixin, @@ -23,7 +23,10 @@ class ProjectExternalStatusCheck(SaveMixin, ObjectDeleteMixin, RESTObject): class ProjectExternalStatusCheckManager( - ListMixin, CreateMixin, UpdateMixin, DeleteMixin, RESTManager + ListMixin[ProjectExternalStatusCheck], + CreateMixin[ProjectExternalStatusCheck], + UpdateMixin[ProjectExternalStatusCheck], + DeleteMixin[ProjectExternalStatusCheck], ): _path = "/projects/{project_id}/external_status_checks" _obj_cls = ProjectExternalStatusCheck @@ -42,7 +45,7 @@ class ProjectMergeRequestStatusCheck(SaveMixin, RESTObject): pass -class ProjectMergeRequestStatusCheckManager(ListMixin, RESTManager): +class ProjectMergeRequestStatusCheckManager(ListMixin[ProjectMergeRequestStatusCheck]): _path = "/projects/{project_id}/merge_requests/{merge_request_iid}/status_checks" _obj_cls = ProjectMergeRequestStatusCheck _from_parent_attrs = {"project_id": "project_id", "merge_request_iid": "iid"} diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py index dab309b07..7a559daa7 100644 --- a/gitlab/v4/objects/tags.py +++ b/gitlab/v4/objects/tags.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin from gitlab.types import RequiredOptional @@ -15,7 +15,7 @@ class ProjectTag(ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class ProjectTagManager(NoUpdateMixin, RESTManager): +class ProjectTagManager(NoUpdateMixin[ProjectTag]): _path = "/projects/{project_id}/repository/tags" _obj_cls = ProjectTag _from_parent_attrs = {"project_id": "id"} @@ -29,7 +29,7 @@ class ProjectProtectedTag(ObjectDeleteMixin, RESTObject): _repr_attr = "name" -class ProjectProtectedTagManager(NoUpdateMixin, RESTManager): +class ProjectProtectedTagManager(NoUpdateMixin[ProjectProtectedTag]): _path = "/projects/{project_id}/protected_tags" _obj_cls = ProjectProtectedTag _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/templates.py b/gitlab/v4/objects/templates.py index 9ff5cc71e..d96e9a1e4 100644 --- a/gitlab/v4/objects/templates.py +++ b/gitlab/v4/objects/templates.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import RetrieveMixin __all__ = [ @@ -29,7 +29,7 @@ class Dockerfile(RESTObject): _id_attr = "name" -class DockerfileManager(RetrieveMixin, RESTManager): +class DockerfileManager(RetrieveMixin[Dockerfile]): _path = "/templates/dockerfiles" _obj_cls = Dockerfile @@ -38,7 +38,7 @@ class Gitignore(RESTObject): _id_attr = "name" -class GitignoreManager(RetrieveMixin, RESTManager): +class GitignoreManager(RetrieveMixin[Gitignore]): _path = "/templates/gitignores" _obj_cls = Gitignore @@ -47,7 +47,7 @@ class Gitlabciyml(RESTObject): _id_attr = "name" -class GitlabciymlManager(RetrieveMixin, RESTManager): +class GitlabciymlManager(RetrieveMixin[Gitlabciyml]): _path = "/templates/gitlab_ci_ymls" _obj_cls = Gitlabciyml @@ -56,7 +56,7 @@ class License(RESTObject): _id_attr = "key" -class LicenseManager(RetrieveMixin, RESTManager): +class LicenseManager(RetrieveMixin[License]): _path = "/templates/licenses" _obj_cls = License _list_filters = ("popular",) @@ -67,7 +67,7 @@ class ProjectDockerfileTemplate(RESTObject): _id_attr = "name" -class ProjectDockerfileTemplateManager(RetrieveMixin, RESTManager): +class ProjectDockerfileTemplateManager(RetrieveMixin[ProjectDockerfileTemplate]): _path = "/projects/{project_id}/templates/dockerfiles" _obj_cls = ProjectDockerfileTemplate _from_parent_attrs = {"project_id": "id"} @@ -77,7 +77,7 @@ class ProjectGitignoreTemplate(RESTObject): _id_attr = "name" -class ProjectGitignoreTemplateManager(RetrieveMixin, RESTManager): +class ProjectGitignoreTemplateManager(RetrieveMixin[ProjectGitignoreTemplate]): _path = "/projects/{project_id}/templates/gitignores" _obj_cls = ProjectGitignoreTemplate _from_parent_attrs = {"project_id": "id"} @@ -87,7 +87,7 @@ class ProjectGitlabciymlTemplate(RESTObject): _id_attr = "name" -class ProjectGitlabciymlTemplateManager(RetrieveMixin, RESTManager): +class ProjectGitlabciymlTemplateManager(RetrieveMixin[ProjectGitlabciymlTemplate]): _path = "/projects/{project_id}/templates/gitlab_ci_ymls" _obj_cls = ProjectGitlabciymlTemplate _from_parent_attrs = {"project_id": "id"} @@ -97,7 +97,7 @@ class ProjectLicenseTemplate(RESTObject): _id_attr = "key" -class ProjectLicenseTemplateManager(RetrieveMixin, RESTManager): +class ProjectLicenseTemplateManager(RetrieveMixin[ProjectLicenseTemplate]): _path = "/projects/{project_id}/templates/licenses" _obj_cls = ProjectLicenseTemplate _from_parent_attrs = {"project_id": "id"} @@ -107,7 +107,7 @@ class ProjectIssueTemplate(RESTObject): _id_attr = "name" -class ProjectIssueTemplateManager(RetrieveMixin, RESTManager): +class ProjectIssueTemplateManager(RetrieveMixin[ProjectIssueTemplate]): _path = "/projects/{project_id}/templates/issues" _obj_cls = ProjectIssueTemplate _from_parent_attrs = {"project_id": "id"} @@ -117,7 +117,7 @@ class ProjectMergeRequestTemplate(RESTObject): _id_attr = "name" -class ProjectMergeRequestTemplateManager(RetrieveMixin, RESTManager): +class ProjectMergeRequestTemplateManager(RetrieveMixin[ProjectMergeRequestTemplate]): _path = "/projects/{project_id}/templates/merge_requests" _obj_cls = ProjectMergeRequestTemplate _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/todos.py b/gitlab/v4/objects/todos.py index 3040db436..577717a13 100644 --- a/gitlab/v4/objects/todos.py +++ b/gitlab/v4/objects/todos.py @@ -2,7 +2,7 @@ from gitlab import cli from gitlab import exceptions as exc -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import DeleteMixin, ListMixin, ObjectDeleteMixin __all__ = [ @@ -35,7 +35,7 @@ def mark_as_done(self, **kwargs: Any) -> Dict[str, Any]: return server_data -class TodoManager(ListMixin, DeleteMixin, RESTManager): +class TodoManager(ListMixin[Todo], DeleteMixin[Todo]): _path = "/todos" _obj_cls = Todo _list_filters = ("action", "author_id", "project_id", "state", "type") diff --git a/gitlab/v4/objects/topics.py b/gitlab/v4/objects/topics.py index 9aaa124c0..914111e3e 100644 --- a/gitlab/v4/objects/topics.py +++ b/gitlab/v4/objects/topics.py @@ -3,7 +3,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -17,7 +17,7 @@ class Topic(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class TopicManager(CRUDMixin, RESTManager): +class TopicManager(CRUDMixin[Topic]): _path = "/topics" _obj_cls = Topic _create_attrs = RequiredOptional( diff --git a/gitlab/v4/objects/triggers.py b/gitlab/v4/objects/triggers.py index 609138047..5fd5e8eac 100644 --- a/gitlab/v4/objects/triggers.py +++ b/gitlab/v4/objects/triggers.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -12,7 +12,7 @@ class ProjectTrigger(SaveMixin, ObjectDeleteMixin, RESTObject): pass -class ProjectTriggerManager(CRUDMixin, RESTManager): +class ProjectTriggerManager(CRUDMixin[ProjectTrigger]): _path = "/projects/{project_id}/triggers" _obj_cls = ProjectTrigger _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index 1a0f84e88..6e10d8fad 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -11,7 +11,7 @@ from gitlab import cli from gitlab import exceptions as exc from gitlab import types -from gitlab.base import RESTManager, RESTObject, RESTObjectList +from gitlab.base import RESTObject, RESTObjectList from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -73,7 +73,11 @@ class CurrentUserEmail(ObjectDeleteMixin, RESTObject): _repr_attr = "email" -class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class CurrentUserEmailManager( + RetrieveMixin[CurrentUserEmail], + CreateMixin[CurrentUserEmail], + DeleteMixin[CurrentUserEmail], +): _path = "/user/emails" _obj_cls = CurrentUserEmail _create_attrs = RequiredOptional(required=("email",)) @@ -83,7 +87,11 @@ class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): pass -class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class CurrentUserGPGKeyManager( + RetrieveMixin[CurrentUserGPGKey], + CreateMixin[CurrentUserGPGKey], + DeleteMixin[CurrentUserGPGKey], +): _path = "/user/gpg_keys" _obj_cls = CurrentUserGPGKey _create_attrs = RequiredOptional(required=("key",)) @@ -93,7 +101,11 @@ class CurrentUserKey(ObjectDeleteMixin, RESTObject): _repr_attr = "title" -class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class CurrentUserKeyManager( + RetrieveMixin[CurrentUserKey], + CreateMixin[CurrentUserKey], + DeleteMixin[CurrentUserKey], +): _path = "/user/keys" _obj_cls = CurrentUserKey _create_attrs = RequiredOptional(required=("title", "key")) @@ -103,7 +115,7 @@ class CurrentUserRunner(RESTObject): pass -class CurrentUserRunnerManager(CreateMixin, RESTManager): +class CurrentUserRunnerManager(CreateMixin[CurrentUserRunner]): _path = "/user/runners" _obj_cls = CurrentUserRunner _types = {"tag_list": types.CommaSeparatedListAttribute} @@ -129,7 +141,9 @@ class CurrentUserStatus(SaveMixin, RESTObject): _repr_attr = "message" -class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager): +class CurrentUserStatusManager( + GetWithoutIdMixin[CurrentUserStatus], UpdateMixin[CurrentUserStatus] +): _path = "/user/status" _obj_cls = CurrentUserStatus _update_attrs = RequiredOptional(optional=("emoji", "message")) @@ -146,7 +160,7 @@ class CurrentUser(RESTObject): status: CurrentUserStatusManager -class CurrentUserManager(GetWithoutIdMixin, RESTManager): +class CurrentUserManager(GetWithoutIdMixin[CurrentUser]): _path = "/user" _obj_cls = CurrentUser @@ -376,7 +390,7 @@ def unban(self, **kwargs: Any) -> Union[Dict[str, Any], requests.Response]: return server_data -class UserManager(CRUDMixin, RESTManager): +class UserManager(CRUDMixin[User]): _path = "/users" _obj_cls = User @@ -452,7 +466,7 @@ class ProjectUser(RESTObject): pass -class ProjectUserManager(ListMixin, RESTManager): +class ProjectUserManager(ListMixin[ProjectUser]): _path = "/projects/{project_id}/users" _obj_cls = ProjectUser _from_parent_attrs = {"project_id": "id"} @@ -464,7 +478,9 @@ class UserEmail(ObjectDeleteMixin, RESTObject): _repr_attr = "email" -class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class UserEmailManager( + RetrieveMixin[UserEmail], CreateMixin[UserEmail], DeleteMixin[UserEmail] +): _path = "/users/{user_id}/emails" _obj_cls = UserEmail _from_parent_attrs = {"user_id": "id"} @@ -480,13 +496,13 @@ class UserStatus(RESTObject): _repr_attr = "message" -class UserStatusManager(GetWithoutIdMixin, RESTManager): +class UserStatusManager(GetWithoutIdMixin[UserStatus]): _path = "/users/{user_id}/status" _obj_cls = UserStatus _from_parent_attrs = {"user_id": "id"} -class UserActivitiesManager(ListMixin, RESTManager): +class UserActivitiesManager(ListMixin[UserActivities]): _path = "/user/activities" _obj_cls = UserActivities @@ -495,7 +511,9 @@ class UserGPGKey(ObjectDeleteMixin, RESTObject): pass -class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class UserGPGKeyManager( + RetrieveMixin[UserGPGKey], CreateMixin[UserGPGKey], DeleteMixin[UserGPGKey] +): _path = "/users/{user_id}/gpg_keys" _obj_cls = UserGPGKey _from_parent_attrs = {"user_id": "id"} @@ -506,14 +524,16 @@ class UserKey(ObjectDeleteMixin, RESTObject): pass -class UserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): +class UserKeyManager( + RetrieveMixin[UserKey], CreateMixin[UserKey], DeleteMixin[UserKey] +): _path = "/users/{user_id}/keys" _obj_cls = UserKey _from_parent_attrs = {"user_id": "id"} _create_attrs = RequiredOptional(required=("title", "key")) -class UserIdentityProviderManager(DeleteMixin, RESTManager): +class UserIdentityProviderManager(DeleteMixin[User]): """Manager for user identities. This manager does not actually manage objects but enables @@ -521,6 +541,7 @@ class UserIdentityProviderManager(DeleteMixin, RESTManager): """ _path = "/users/{user_id}/identities" + _obj_cls = User _from_parent_attrs = {"user_id": "id"} @@ -528,7 +549,7 @@ class UserImpersonationToken(ObjectDeleteMixin, RESTObject): pass -class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): +class UserImpersonationTokenManager(NoUpdateMixin[UserImpersonationToken]): _path = "/users/{user_id}/impersonation_tokens" _obj_cls = UserImpersonationToken _from_parent_attrs = {"user_id": "id"} @@ -543,7 +564,7 @@ class UserMembership(RESTObject): _id_attr = "source_id" -class UserMembershipManager(RetrieveMixin, RESTManager): +class UserMembershipManager(RetrieveMixin[UserMembership]): _path = "/users/{user_id}/memberships" _obj_cls = UserMembership _from_parent_attrs = {"user_id": "id"} @@ -555,7 +576,7 @@ class UserProject(RESTObject): pass -class UserProjectManager(ListMixin, CreateMixin, RESTManager): +class UserProjectManager(ListMixin[UserProject], CreateMixin[UserProject]): _path = "/projects/user/{user_id}" _obj_cls = UserProject _from_parent_attrs = {"user_id": "id"} @@ -600,7 +621,7 @@ class UserProjectManager(ListMixin, CreateMixin, RESTManager): "id_before", ) - def list(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: + def list(self, **kwargs: Any) -> Union[RESTObjectList, List[UserProject]]: """Retrieve a list of objects. Args: @@ -622,14 +643,14 @@ def list(self, **kwargs: Any) -> Union[RESTObjectList, List[RESTObject]]: path = f"/users/{self._parent.id}/projects" else: path = f"/users/{self._from_parent_attrs['user_id']}/projects" - return ListMixin.list(self, path=path, **kwargs) + return super().list(path=path, **kwargs) class StarredProject(RESTObject): pass -class StarredProjectManager(ListMixin, RESTManager): +class StarredProjectManager(ListMixin[StarredProject]): _path = "/users/{user_id}/starred_projects" _obj_cls = StarredProject _from_parent_attrs = {"user_id": "id"} @@ -651,13 +672,13 @@ class StarredProjectManager(ListMixin, RESTManager): ) -class UserFollowersManager(ListMixin, RESTManager): +class UserFollowersManager(ListMixin[User]): _path = "/users/{user_id}/followers" _obj_cls = User _from_parent_attrs = {"user_id": "id"} -class UserFollowingManager(ListMixin, RESTManager): +class UserFollowingManager(ListMixin[User]): _path = "/users/{user_id}/following" _obj_cls = User _from_parent_attrs = {"user_id": "id"} diff --git a/gitlab/v4/objects/variables.py b/gitlab/v4/objects/variables.py index b5567763c..bae2be22b 100644 --- a/gitlab/v4/objects/variables.py +++ b/gitlab/v4/objects/variables.py @@ -5,7 +5,7 @@ https://docs.gitlab.com/ee/api/group_level_variables.html """ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin from gitlab.types import RequiredOptional @@ -23,7 +23,7 @@ class Variable(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "key" -class VariableManager(CRUDMixin, RESTManager): +class VariableManager(CRUDMixin[Variable]): _path = "/admin/ci/variables" _obj_cls = Variable _create_attrs = RequiredOptional( @@ -38,7 +38,7 @@ class GroupVariable(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "key" -class GroupVariableManager(CRUDMixin, RESTManager): +class GroupVariableManager(CRUDMixin[GroupVariable]): _path = "/groups/{group_id}/variables" _obj_cls = GroupVariable _from_parent_attrs = {"group_id": "id"} @@ -54,7 +54,7 @@ class ProjectVariable(SaveMixin, ObjectDeleteMixin, RESTObject): _id_attr = "key" -class ProjectVariableManager(CRUDMixin, RESTManager): +class ProjectVariableManager(CRUDMixin[ProjectVariable]): _path = "/projects/{project_id}/variables" _obj_cls = ProjectVariable _from_parent_attrs = {"project_id": "id"} diff --git a/gitlab/v4/objects/wikis.py b/gitlab/v4/objects/wikis.py index 3be21aefb..2a7e73a1f 100644 --- a/gitlab/v4/objects/wikis.py +++ b/gitlab/v4/objects/wikis.py @@ -1,4 +1,4 @@ -from gitlab.base import RESTManager, RESTObject +from gitlab.base import RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin, UploadMixin from gitlab.types import RequiredOptional @@ -16,7 +16,7 @@ class ProjectWiki(SaveMixin, ObjectDeleteMixin, UploadMixin, RESTObject): _upload_path = "/projects/{project_id}/wikis/attachments" -class ProjectWikiManager(CRUDMixin, RESTManager): +class ProjectWikiManager(CRUDMixin[ProjectWiki]): _path = "/projects/{project_id}/wikis" _obj_cls = ProjectWiki _from_parent_attrs = {"project_id": "id"} @@ -33,7 +33,7 @@ class GroupWiki(SaveMixin, ObjectDeleteMixin, UploadMixin, RESTObject): _upload_path = "/groups/{group_id}/wikis/attachments" -class GroupWikiManager(CRUDMixin, RESTManager): +class GroupWikiManager(CRUDMixin[GroupWiki]): _path = "/groups/{group_id}/wikis" _obj_cls = GroupWiki _from_parent_attrs = {"group_id": "id"} diff --git a/tests/functional/api/test_merge_requests.py b/tests/functional/api/test_merge_requests.py index 961605c83..cfa7fde80 100644 --- a/tests/functional/api/test_merge_requests.py +++ b/tests/functional/api/test_merge_requests.py @@ -180,7 +180,7 @@ def test_merge_request_reset_approvals(gitlab_url, project): # Pause to let GL catch up (happens on hosted too, sometimes takes a while for server to be ready to merge) time.sleep(5) - mr = bot_project.mergerequests.list()[0] + mr = bot_project.mergerequests.list()[0] # type: ignore[index] assert mr.reset_approvals() diff --git a/tests/unit/meta/test_abstract_attrs.py b/tests/unit/meta/test_abstract_attrs.py new file mode 100644 index 000000000..e43a81b7b --- /dev/null +++ b/tests/unit/meta/test_abstract_attrs.py @@ -0,0 +1,41 @@ +""" +Ensure that RESTManager subclasses exported to gitlab.v4.objects +are defining the _path and _obj_cls attributes. + +Only check using `hasattr` as if incorrect type is assigned the type +checker will raise an error. +""" + +from __future__ import annotations + +from inspect import getmembers + +import gitlab.v4.objects +from gitlab.base import RESTManager + + +def test_rest_manager_abstract_attrs() -> None: + without_path: list[str] = [] + without_obj_cls: list[str] = [] + + for key, member in getmembers(gitlab.v4.objects): + if not isinstance(member, type): + continue + + if not issubclass(member, RESTManager): + continue + + if not hasattr(member, "_path"): + without_path.append(key) + + if not hasattr(member, "_obj_cls"): + without_obj_cls.append(key) + + assert not without_path, ( + "RESTManager subclasses missing '_path' attribute: " + f"{', '.join(without_path)}" + ) + assert not without_obj_cls, ( + "RESTManager subclasses missing '_obj_cls' attribute: " + f"{', '.join(without_obj_cls)}" + ) diff --git a/tests/unit/meta/test_mro.py b/tests/unit/meta/test_mro.py index d7dd0046f..a0f8e6068 100644 --- a/tests/unit/meta/test_mro.py +++ b/tests/unit/meta/test_mro.py @@ -44,6 +44,7 @@ class Wrongv4Object(RESTObject, Mixin): """ import inspect +from typing import Generic import pytest @@ -107,8 +108,13 @@ class definition. if has_base: filename = inspect.getfile(class_value) # NOTE(jlvillal): The very last item 'mro[-1]' is always going - # to be 'object'. That is why we are checking 'mro[-2]'. - if mro[-2].__module__ != "gitlab.base": + # to be 'object'. The second to last might be typing.Generic. + # That is why we are checking either 'mro[-3]' or 'mro[-2]'. + index_to_check = -2 + if mro[index_to_check] == Generic: + index_to_check -= 1 + + if mro[index_to_check].__module__ != "gitlab.base": failed_messages.append( ( f"class definition for {class_name!r} in file {filename!r} " diff --git a/tests/unit/mixins/test_meta_mixins.py b/tests/unit/mixins/test_meta_mixins.py index 4c8845b69..5144a17bc 100644 --- a/tests/unit/mixins/test_meta_mixins.py +++ b/tests/unit/mixins/test_meta_mixins.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock + from gitlab.mixins import ( CreateMixin, CRUDMixin, @@ -12,9 +14,10 @@ def test_retrieve_mixin(): class M(RetrieveMixin): - pass + _obj_cls = object + _path = "/test" - obj = M() + obj = M(MagicMock()) assert hasattr(obj, "list") assert hasattr(obj, "get") assert not hasattr(obj, "create") @@ -26,9 +29,10 @@ class M(RetrieveMixin): def test_crud_mixin(): class M(CRUDMixin): - pass + _obj_cls = object + _path = "/test" - obj = M() + obj = M(MagicMock()) assert hasattr(obj, "get") assert hasattr(obj, "list") assert hasattr(obj, "create") @@ -43,9 +47,10 @@ class M(CRUDMixin): def test_no_update_mixin(): class M(NoUpdateMixin): - pass + _obj_cls = object + _path = "/test" - obj = M() + obj = M(MagicMock()) assert hasattr(obj, "get") assert hasattr(obj, "list") assert hasattr(obj, "create") diff --git a/tests/unit/mixins/test_object_mixins_attributes.py b/tests/unit/mixins/test_object_mixins_attributes.py index 962754b82..99f301933 100644 --- a/tests/unit/mixins/test_object_mixins_attributes.py +++ b/tests/unit/mixins/test_object_mixins_attributes.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock + from gitlab.mixins import ( AccessRequestMixin, SetMixin, @@ -47,15 +49,15 @@ class TestClass(TimeTrackingMixin): def test_set_mixin(): class TestClass(SetMixin): - pass + _obj_cls = object + _path = "/test" - obj = TestClass() + obj = TestClass(MagicMock()) assert hasattr(obj, "set") def test_user_agent_detail_mixin(): - class TestClass(UserAgentDetailMixin): - pass + class TestClass(UserAgentDetailMixin): ... obj = TestClass() assert hasattr(obj, "user_agent_detail") diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index eaa3908b5..5a1551ad8 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -168,7 +168,8 @@ def error(self, message): class Fake: _id_attr = None - class FakeManager(gitlab.base.RESTManager, CreateMixin, UpdateMixin): + class FakeManager(CreateMixin, UpdateMixin, gitlab.base.RESTManager): + _path = "/fake" _obj_cls = Fake _create_attrs = RequiredOptional( required=("create",),