diff --git a/gitlab/v4/objects/audit_events.py b/gitlab/v4/objects/audit_events.py index ab632bb6f..649dc9dd3 100644 --- a/gitlab/v4/objects/audit_events.py +++ b/gitlab/v4/objects/audit_events.py @@ -2,6 +2,8 @@ GitLab API: https://docs.gitlab.com/ee/api/audit_events.html """ +from typing import Any, cast, Union + from gitlab.base import RESTManager, RESTObject from gitlab.mixins import RetrieveMixin @@ -26,6 +28,9 @@ class AuditEventManager(RetrieveMixin, RESTManager): _obj_cls = AuditEvent _list_filters = ("created_after", "created_before", "entity_type", "entity_id") + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> AuditEvent: + return cast(AuditEvent, super().get(id=id, lazy=lazy, **kwargs)) + class GroupAuditEvent(RESTObject): _id_attr = "id" @@ -37,6 +42,11 @@ class GroupAuditEventManager(RetrieveMixin, RESTManager): _from_parent_attrs = {"group_id": "id"} _list_filters = ("created_after", "created_before") + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupAuditEvent: + return cast(GroupAuditEvent, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectAuditEvent(RESTObject): _id_attr = "id" @@ -48,6 +58,11 @@ class ProjectAuditEventManager(RetrieveMixin, RESTManager): _from_parent_attrs = {"project_id": "id"} _list_filters = ("created_after", "created_before") + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectAuditEvent: + return cast(ProjectAuditEvent, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectAudit(ProjectAuditEvent): pass diff --git a/gitlab/v4/objects/award_emojis.py b/gitlab/v4/objects/award_emojis.py index 41b2d7d6a..e4ad370c6 100644 --- a/gitlab/v4/objects/award_emojis.py +++ b/gitlab/v4/objects/award_emojis.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin @@ -27,6 +29,11 @@ class ProjectIssueAwardEmojiManager(NoUpdateMixin, RESTManager): _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} _create_attrs = RequiredOptional(required=("name",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueAwardEmoji: + return cast(ProjectIssueAwardEmoji, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectIssueNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -42,6 +49,11 @@ class ProjectIssueNoteAwardEmojiManager(NoUpdateMixin, RESTManager): } _create_attrs = RequiredOptional(required=("name",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueNoteAwardEmoji: + return cast(ProjectIssueNoteAwardEmoji, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMergeRequestAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -53,6 +65,13 @@ class ProjectMergeRequestAwardEmojiManager(NoUpdateMixin, RESTManager): _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} _create_attrs = RequiredOptional(required=("name",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestAwardEmoji: + return cast( + ProjectMergeRequestAwardEmoji, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectMergeRequestNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -68,6 +87,13 @@ class ProjectMergeRequestNoteAwardEmojiManager(NoUpdateMixin, RESTManager): } _create_attrs = RequiredOptional(required=("name",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestNoteAwardEmoji: + return cast( + ProjectMergeRequestNoteAwardEmoji, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectSnippetAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -79,6 +105,11 @@ class ProjectSnippetAwardEmojiManager(NoUpdateMixin, RESTManager): _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} _create_attrs = RequiredOptional(required=("name",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectSnippetAwardEmoji: + return cast(ProjectSnippetAwardEmoji, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectSnippetNoteAwardEmoji(ObjectDeleteMixin, RESTObject): pass @@ -93,3 +124,10 @@ class ProjectSnippetNoteAwardEmojiManager(NoUpdateMixin, RESTManager): "note_id": "id", } _create_attrs = RequiredOptional(required=("name",)) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectSnippetNoteAwardEmoji: + return cast( + ProjectSnippetNoteAwardEmoji, super().get(id=id, lazy=lazy, **kwargs) + ) diff --git a/gitlab/v4/objects/badges.py b/gitlab/v4/objects/badges.py index dd3ea49e5..4dee75ac0 100644 --- a/gitlab/v4/objects/badges.py +++ b/gitlab/v4/objects/badges.py @@ -22,6 +22,9 @@ class GroupBadgeManager(BadgeRenderMixin, CRUDMixin, RESTManager): _create_attrs = RequiredOptional(required=("link_url", "image_url")) _update_attrs = RequiredOptional(optional=("link_url", "image_url")) + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> GroupBadge: + return cast(GroupBadge, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectBadge(SaveMixin, ObjectDeleteMixin, RESTObject): pass diff --git a/gitlab/v4/objects/branches.py b/gitlab/v4/objects/branches.py index 407765c0c..d06d6b44f 100644 --- a/gitlab/v4/objects/branches.py +++ b/gitlab/v4/objects/branches.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin @@ -19,6 +21,11 @@ class ProjectBranchManager(NoUpdateMixin, RESTManager): _from_parent_attrs = {"project_id": "id"} _create_attrs = RequiredOptional(required=("branch", "ref")) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectBranch: + return cast(ProjectBranch, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectProtectedBranch(ObjectDeleteMixin, RESTObject): _id_attr = "name" @@ -40,3 +47,8 @@ class ProjectProtectedBranchManager(NoUpdateMixin, RESTManager): "code_owner_approval_required", ), ) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectProtectedBranch: + return cast(ProjectProtectedBranch, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/clusters.py b/gitlab/v4/objects/clusters.py index 4821b70f5..5491654fa 100644 --- a/gitlab/v4/objects/clusters.py +++ b/gitlab/v4/objects/clusters.py @@ -1,4 +1,4 @@ -from typing import Any, cast, Dict, Optional +from typing import Any, cast, Dict, Optional, Union from gitlab import exceptions as exc from gitlab.base import RequiredOptional, RESTManager, RESTObject @@ -57,6 +57,11 @@ def create( path = f"{self.path}/user" return cast(GroupCluster, CreateMixin.create(self, data, path=path, **kwargs)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupCluster: + return cast(GroupCluster, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectCluster(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -102,3 +107,8 @@ def create( """ path = f"{self.path}/user" return cast(ProjectCluster, CreateMixin.create(self, data, path=path, **kwargs)) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectCluster: + return cast(ProjectCluster, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py index 330182461..b93dcdf71 100644 --- a/gitlab/v4/objects/commits.py +++ b/gitlab/v4/objects/commits.py @@ -151,6 +151,11 @@ class ProjectCommitManager(RetrieveMixin, CreateMixin, RESTManager): optional=("author_email", "author_name"), ) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectCommit: + return cast(ProjectCommit, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectCommitComment(RESTObject): _id_attr = None diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py index caf8f52c4..892574a41 100644 --- a/gitlab/v4/objects/container_registry.py +++ b/gitlab/v4/objects/container_registry.py @@ -1,4 +1,4 @@ -from typing import Any, TYPE_CHECKING +from typing import Any, cast, TYPE_CHECKING, Union from gitlab import cli from gitlab import exceptions as exc @@ -60,3 +60,8 @@ def delete_in_bulk(self, name_regex_delete: str, **kwargs: Any) -> None: if TYPE_CHECKING: assert self.path is not None self.gitlab.http_delete(self.path, query_data=data, **kwargs) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectRegistryTag: + return cast(ProjectRegistryTag, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/custom_attributes.py b/gitlab/v4/objects/custom_attributes.py index aed19652f..d06161474 100644 --- a/gitlab/v4/objects/custom_attributes.py +++ b/gitlab/v4/objects/custom_attributes.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RESTManager, RESTObject from gitlab.mixins import DeleteMixin, ObjectDeleteMixin, RetrieveMixin, SetMixin @@ -20,6 +22,11 @@ class GroupCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTMana _obj_cls = GroupCustomAttribute _from_parent_attrs = {"group_id": "id"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupCustomAttribute: + return cast(GroupCustomAttribute, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectCustomAttribute(ObjectDeleteMixin, RESTObject): _id_attr = "key" @@ -30,6 +37,11 @@ class ProjectCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTMa _obj_cls = ProjectCustomAttribute _from_parent_attrs = {"project_id": "id"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectCustomAttribute: + return cast(ProjectCustomAttribute, super().get(id=id, lazy=lazy, **kwargs)) + class UserCustomAttribute(ObjectDeleteMixin, RESTObject): _id_attr = "key" @@ -39,3 +51,8 @@ class UserCustomAttributeManager(RetrieveMixin, SetMixin, DeleteMixin, RESTManag _path = "/users/{user_id}/custom_attributes" _obj_cls = UserCustomAttribute _from_parent_attrs = {"user_id": "id"} + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> UserCustomAttribute: + return cast(UserCustomAttribute, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py index 8b4a7beb6..9aee699c9 100644 --- a/gitlab/v4/objects/deployments.py +++ b/gitlab/v4/objects/deployments.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin @@ -28,3 +30,8 @@ class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTMana _create_attrs = RequiredOptional( required=("sha", "ref", "tag", "status", "environment") ) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectDeployment: + return cast(ProjectDeployment, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py index 94f0a3993..fa874c436 100644 --- a/gitlab/v4/objects/discussions.py +++ b/gitlab/v4/objects/discussions.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import CreateMixin, RetrieveMixin, SaveMixin, UpdateMixin @@ -30,6 +32,11 @@ class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): _from_parent_attrs = {"project_id": "project_id", "commit_id": "id"} _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectCommitDiscussion: + return cast(ProjectCommitDiscussion, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectIssueDiscussion(RESTObject): notes: ProjectIssueDiscussionNoteManager @@ -41,6 +48,11 @@ class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueDiscussion: + return cast(ProjectIssueDiscussion, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): notes: ProjectMergeRequestDiscussionNoteManager @@ -57,6 +69,13 @@ class ProjectMergeRequestDiscussionManager( ) _update_attrs = RequiredOptional(required=("resolved",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestDiscussion: + return cast( + ProjectMergeRequestDiscussion, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectSnippetDiscussion(RESTObject): notes: ProjectSnippetDiscussionNoteManager @@ -67,3 +86,8 @@ class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): _obj_cls = ProjectSnippetDiscussion _from_parent_attrs = {"project_id": "project_id", "snippet_id": "id"} _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectSnippetDiscussion: + return cast(ProjectSnippetDiscussion, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py index 6eec0694f..35f2fb24a 100644 --- a/gitlab/v4/objects/environments.py +++ b/gitlab/v4/objects/environments.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any, cast, Dict, Union import requests @@ -48,3 +48,8 @@ class ProjectEnvironmentManager( _from_parent_attrs = {"project_id": "id"} _create_attrs = RequiredOptional(required=("name",), optional=("external_url",)) _update_attrs = RequiredOptional(optional=("name", "external_url")) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectEnvironment: + return cast(ProjectEnvironment, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/events.py b/gitlab/v4/objects/events.py index 7af488d9c..b7d8fd14d 100644 --- a/gitlab/v4/objects/events.py +++ b/gitlab/v4/objects/events.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RESTManager, RESTObject from gitlab.mixins import ListMixin, RetrieveMixin @@ -45,6 +47,13 @@ class GroupEpicResourceLabelEventManager(RetrieveMixin, RESTManager): _obj_cls = GroupEpicResourceLabelEvent _from_parent_attrs = {"group_id": "group_id", "epic_id": "id"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupEpicResourceLabelEvent: + return cast( + GroupEpicResourceLabelEvent, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectEvent(Event): pass @@ -65,6 +74,13 @@ class ProjectIssueResourceLabelEventManager(RetrieveMixin, RESTManager): _obj_cls = ProjectIssueResourceLabelEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueResourceLabelEvent: + return cast( + ProjectIssueResourceLabelEvent, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectIssueResourceMilestoneEvent(RESTObject): pass @@ -75,6 +91,13 @@ class ProjectIssueResourceMilestoneEventManager(RetrieveMixin, RESTManager): _obj_cls = ProjectIssueResourceMilestoneEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueResourceMilestoneEvent: + return cast( + ProjectIssueResourceMilestoneEvent, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectIssueResourceStateEvent(RESTObject): pass @@ -85,6 +108,13 @@ class ProjectIssueResourceStateEventManager(RetrieveMixin, RESTManager): _obj_cls = ProjectIssueResourceStateEvent _from_parent_attrs = {"project_id": "project_id", "issue_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueResourceStateEvent: + return cast( + ProjectIssueResourceStateEvent, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectMergeRequestResourceLabelEvent(RESTObject): pass @@ -95,6 +125,14 @@ class ProjectMergeRequestResourceLabelEventManager(RetrieveMixin, RESTManager): _obj_cls = ProjectMergeRequestResourceLabelEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestResourceLabelEvent: + return cast( + ProjectMergeRequestResourceLabelEvent, + super().get(id=id, lazy=lazy, **kwargs), + ) + class ProjectMergeRequestResourceMilestoneEvent(RESTObject): pass @@ -105,6 +143,14 @@ class ProjectMergeRequestResourceMilestoneEventManager(RetrieveMixin, RESTManage _obj_cls = ProjectMergeRequestResourceMilestoneEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestResourceMilestoneEvent: + return cast( + ProjectMergeRequestResourceMilestoneEvent, + super().get(id=id, lazy=lazy, **kwargs), + ) + class ProjectMergeRequestResourceStateEvent(RESTObject): pass @@ -115,6 +161,14 @@ class ProjectMergeRequestResourceStateEventManager(RetrieveMixin, RESTManager): _obj_cls = ProjectMergeRequestResourceStateEvent _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestResourceStateEvent: + return cast( + ProjectMergeRequestResourceStateEvent, + super().get(id=id, lazy=lazy, **kwargs), + ) + class UserEvent(Event): pass diff --git a/gitlab/v4/objects/hooks.py b/gitlab/v4/objects/hooks.py index 00dcfee14..0b0092e3c 100644 --- a/gitlab/v4/objects/hooks.py +++ b/gitlab/v4/objects/hooks.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import CRUDMixin, NoUpdateMixin, ObjectDeleteMixin, SaveMixin @@ -21,6 +23,9 @@ class HookManager(NoUpdateMixin, RESTManager): _obj_cls = Hook _create_attrs = RequiredOptional(required=("url",)) + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Hook: + return cast(Hook, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectHook(SaveMixin, ObjectDeleteMixin, RESTObject): _short_print_attr = "url" @@ -63,6 +68,11 @@ class ProjectHookManager(CRUDMixin, RESTManager): ), ) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectHook: + return cast(ProjectHook, super().get(id=id, lazy=lazy, **kwargs)) + class GroupHook(SaveMixin, ObjectDeleteMixin, RESTObject): _short_print_attr = "url" @@ -112,3 +122,6 @@ class GroupHookManager(CRUDMixin, RESTManager): "token", ), ) + + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> GroupHook: + return cast(GroupHook, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py index a0abb0028..8fa2bb318 100644 --- a/gitlab/v4/objects/members.py +++ b/gitlab/v4/objects/members.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab import types from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import ( @@ -39,6 +41,11 @@ class GroupMemberManager(CRUDMixin, RESTManager): ) _types = {"user_ids": types.ListAttribute} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupMember: + return cast(GroupMember, super().get(id=id, lazy=lazy, **kwargs)) + class GroupBillableMember(ObjectDeleteMixin, RESTObject): _short_print_attr = "username" @@ -68,6 +75,11 @@ class GroupMemberAllManager(RetrieveMixin, RESTManager): _obj_cls = GroupMember _from_parent_attrs = {"group_id": "id"} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> GroupMember: + return cast(GroupMember, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMember(SaveMixin, ObjectDeleteMixin, RESTObject): _short_print_attr = "username" @@ -85,8 +97,18 @@ class ProjectMemberManager(CRUDMixin, RESTManager): ) _types = {"user_ids": types.ListAttribute} + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMember: + return cast(ProjectMember, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMemberAllManager(RetrieveMixin, RESTManager): _path = "/projects/{project_id}/members/all" _obj_cls = ProjectMember _from_parent_attrs = {"project_id": "id"} + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMember: + return cast(ProjectMember, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index 672d0b774..068f25df7 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -480,3 +480,8 @@ class ProjectMergeRequestDiffManager(RetrieveMixin, RESTManager): _path = "/projects/{project_id}/merge_requests/{mr_iid}/versions" _obj_cls = ProjectMergeRequestDiff _from_parent_attrs = {"project_id": "project_id", "mr_iid": "iid"} + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestDiff: + return cast(ProjectMergeRequestDiff, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/notes.py b/gitlab/v4/objects/notes.py index 9dd05cc15..c4055ad65 100644 --- a/gitlab/v4/objects/notes.py +++ b/gitlab/v4/objects/notes.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import ( CreateMixin, @@ -46,6 +48,11 @@ class ProjectNoteManager(RetrieveMixin, RESTManager): _from_parent_attrs = {"project_id": "id"} _create_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectNote: + return cast(ProjectNote, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectCommitDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -69,6 +76,13 @@ class ProjectCommitDiscussionNoteManager( ) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectCommitDiscussionNote: + return cast( + ProjectCommitDiscussionNote, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectIssueNoteAwardEmojiManager @@ -81,6 +95,11 @@ class ProjectIssueNoteManager(CRUDMixin, RESTManager): _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueNote: + return cast(ProjectIssueNote, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectIssueDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -101,6 +120,11 @@ class ProjectIssueDiscussionNoteManager( _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectIssueDiscussionNote: + return cast(ProjectIssueDiscussionNote, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectMergeRequestNoteAwardEmojiManager @@ -113,6 +137,11 @@ class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager): _create_attrs = RequiredOptional(required=("body",)) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestNote: + return cast(ProjectMergeRequestNote, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectMergeRequestDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -134,6 +163,13 @@ class ProjectMergeRequestDiscussionNoteManager( _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectMergeRequestDiscussionNote: + return cast( + ProjectMergeRequestDiscussionNote, super().get(id=id, lazy=lazy, **kwargs) + ) + class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectMergeRequestNoteAwardEmojiManager @@ -146,6 +182,11 @@ class ProjectSnippetNoteManager(CRUDMixin, RESTManager): _create_attrs = RequiredOptional(required=("body",)) _update_attrs = RequiredOptional(required=("body",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectSnippetNote: + return cast(ProjectSnippetNote, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectSnippetDiscussionNote(SaveMixin, ObjectDeleteMixin, RESTObject): pass @@ -166,3 +207,10 @@ class ProjectSnippetDiscussionNoteManager( } _create_attrs = RequiredOptional(required=("body",), optional=("created_at",)) _update_attrs = RequiredOptional(required=("body",)) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectSnippetDiscussionNote: + return cast( + ProjectSnippetDiscussionNote, super().get(id=id, lazy=lazy, **kwargs) + ) diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py index d9923035c..00620677a 100644 --- a/gitlab/v4/objects/packages.py +++ b/gitlab/v4/objects/packages.py @@ -5,7 +5,7 @@ """ from pathlib import Path -from typing import Any, Callable, Optional, TYPE_CHECKING, Union +from typing import Any, Callable, cast, Optional, TYPE_CHECKING, Union import requests @@ -167,6 +167,11 @@ class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager): "package_name", ) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectPackage: + return cast(ProjectPackage, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectPackageFile(RESTObject): pass diff --git a/gitlab/v4/objects/tags.py b/gitlab/v4/objects/tags.py index a85f0e3d6..c76799d20 100644 --- a/gitlab/v4/objects/tags.py +++ b/gitlab/v4/objects/tags.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RequiredOptional, RESTManager, RESTObject from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin @@ -22,6 +24,9 @@ class ProjectTagManager(NoUpdateMixin, RESTManager): required=("tag_name", "ref"), optional=("message",) ) + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> ProjectTag: + return cast(ProjectTag, super().get(id=id, lazy=lazy, **kwargs)) + class ProjectProtectedTag(ObjectDeleteMixin, RESTObject): _id_attr = "name" @@ -35,3 +40,8 @@ class ProjectProtectedTagManager(NoUpdateMixin, RESTManager): _create_attrs = RequiredOptional( required=("name",), optional=("create_access_level",) ) + + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> ProjectProtectedTag: + return cast(ProjectProtectedTag, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/templates.py b/gitlab/v4/objects/templates.py index 04de46343..bbe2ae6c1 100644 --- a/gitlab/v4/objects/templates.py +++ b/gitlab/v4/objects/templates.py @@ -1,3 +1,5 @@ +from typing import Any, cast, Union + from gitlab.base import RESTManager, RESTObject from gitlab.mixins import RetrieveMixin @@ -21,6 +23,9 @@ class DockerfileManager(RetrieveMixin, RESTManager): _path = "/templates/dockerfiles" _obj_cls = Dockerfile + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Dockerfile: + return cast(Dockerfile, super().get(id=id, lazy=lazy, **kwargs)) + class Gitignore(RESTObject): _id_attr = "name" @@ -30,6 +35,9 @@ class GitignoreManager(RetrieveMixin, RESTManager): _path = "/templates/gitignores" _obj_cls = Gitignore + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Gitignore: + return cast(Gitignore, super().get(id=id, lazy=lazy, **kwargs)) + class Gitlabciyml(RESTObject): _id_attr = "name" @@ -39,6 +47,11 @@ class GitlabciymlManager(RetrieveMixin, RESTManager): _path = "/templates/gitlab_ci_ymls" _obj_cls = Gitlabciyml + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> Gitlabciyml: + return cast(Gitlabciyml, super().get(id=id, lazy=lazy, **kwargs)) + class License(RESTObject): _id_attr = "key" @@ -49,3 +62,6 @@ class LicenseManager(RetrieveMixin, RESTManager): _obj_cls = License _list_filters = ("popular",) _optional_get_attrs = ("project", "fullname") + + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> License: + return cast(License, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index ac75284af..8649cbafb 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -74,6 +74,11 @@ class CurrentUserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManag _obj_cls = CurrentUserEmail _create_attrs = RequiredOptional(required=("email",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> CurrentUserEmail: + return cast(CurrentUserEmail, super().get(id=id, lazy=lazy, **kwargs)) + class CurrentUserGPGKey(ObjectDeleteMixin, RESTObject): pass @@ -84,6 +89,11 @@ class CurrentUserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTMana _obj_cls = CurrentUserGPGKey _create_attrs = RequiredOptional(required=("key",)) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> CurrentUserGPGKey: + return cast(CurrentUserGPGKey, super().get(id=id, lazy=lazy, **kwargs)) + class CurrentUserKey(ObjectDeleteMixin, RESTObject): _short_print_attr = "title" @@ -94,6 +104,11 @@ class CurrentUserKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager _obj_cls = CurrentUserKey _create_attrs = RequiredOptional(required=("title", "key")) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> CurrentUserKey: + return cast(CurrentUserKey, super().get(id=id, lazy=lazy, **kwargs)) + class CurrentUserStatus(SaveMixin, RESTObject): _id_attr = None @@ -357,6 +372,9 @@ class UserEmailManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): _from_parent_attrs = {"user_id": "id"} _create_attrs = RequiredOptional(required=("email",)) + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> UserEmail: + return cast(UserEmail, super().get(id=id, lazy=lazy, **kwargs)) + class UserActivities(RESTObject): _id_attr = "username" @@ -388,6 +406,9 @@ class UserGPGKeyManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManager): _from_parent_attrs = {"user_id": "id"} _create_attrs = RequiredOptional(required=("key",)) + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> UserGPGKey: + return cast(UserGPGKey, super().get(id=id, lazy=lazy, **kwargs)) + class UserKey(ObjectDeleteMixin, RESTObject): pass @@ -424,6 +445,11 @@ class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): ) _list_filters = ("state",) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> UserImpersonationToken: + return cast(UserImpersonationToken, super().get(id=id, lazy=lazy, **kwargs)) + class UserMembership(RESTObject): _id_attr = "source_id" @@ -435,6 +461,11 @@ class UserMembershipManager(RetrieveMixin, RESTManager): _from_parent_attrs = {"user_id": "id"} _list_filters = ("type",) + def get( + self, id: Union[str, int], lazy: bool = False, **kwargs: Any + ) -> UserMembership: + return cast(UserMembership, super().get(id=id, lazy=lazy, **kwargs)) + # Having this outside projects avoids circular imports due to ProjectUser class UserProject(RESTObject): diff --git a/pyproject.toml b/pyproject.toml index 160232c22..f48ed5f65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ module = [ "setup", "tests.functional.*", "tests.functional.api.*", + "tests.meta.*", "tests.unit.*", "tests.smoke.*" ] diff --git a/tests/meta/__init__.py b/tests/meta/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/meta/test_ensure_type_hints.py b/tests/meta/test_ensure_type_hints.py new file mode 100644 index 000000000..f647b45a7 --- /dev/null +++ b/tests/meta/test_ensure_type_hints.py @@ -0,0 +1,85 @@ +""" +Ensure type-hints are setup correctly and detect if missing functions. + +Original notes by John L. Villalovos + +""" +import inspect +from typing import Tuple, Type + +import toml + +import gitlab.mixins +import gitlab.v4.objects + + +def pytest_generate_tests(metafunc): + """Find all of the classes in gitlab.v4.objects and pass them to our test + function""" + + # Ignore any modules that we are ignoring in our pyproject.toml + excluded_modules = set() + with open("pyproject.toml", "r") as in_file: + pyproject = toml.load(in_file) + overrides = pyproject.get("tool", {}).get("mypy", {}).get("overrides", []) + for override in overrides: + if not override.get("ignore_errors"): + continue + for module in override.get("module", []): + if module.startswith("gitlab.v4.objects"): + excluded_modules.add(module) + + class_info_list = [] + for module_name, module_value in inspect.getmembers(gitlab.v4.objects): + if not inspect.ismodule(module_value): + # We only care about the modules + continue + # Iterate through all the classes in our module + for class_name, class_value in inspect.getmembers(module_value): + if not inspect.isclass(class_value): + continue + + module_name = class_value.__module__ + # Ignore modules that mypy is ignoring + if module_name in excluded_modules: + continue + + # Ignore imported classes from gitlab.base + if module_name == "gitlab.base": + continue + + class_info_list.append((class_name, class_value)) + + metafunc.parametrize("class_info", class_info_list) + + +class TestTypeHints: + def test_check_get_function_type_hints(self, class_info: Tuple[str, Type]): + """Ensure classes derived from GetMixin have defined a 'get()' method with + correct type-hints. + """ + class_name, class_value = class_info + if not class_name.endswith("Manager"): + return + + mro = class_value.mro() + # The class needs to be derived from GetMixin or we ignore it + if gitlab.mixins.GetMixin not in mro: + return + + obj_cls = class_value._obj_cls + signature = inspect.signature(class_value.get) + filename = inspect.getfile(class_value) + + fail_message = ( + f"class definition for {class_name!r} in file {filename!r} " + f"must have defined a 'get' method with a return annotation of " + f"{obj_cls} but found {signature.return_annotation}\n" + f"Recommend adding the followinng method:\n" + f"def get(\n" + f" self, id: Union[str, int], lazy: bool = False, **kwargs: Any\n" + f" ) -> {obj_cls.__name__}:\n" + f" return cast({obj_cls.__name__}, super().get(id=id, lazy=lazy, " + f"**kwargs))\n" + ) + assert obj_cls == signature.return_annotation, fail_message diff --git a/tests/unit/objects/test_mro.py b/tests/meta/test_mro.py similarity index 100% rename from tests/unit/objects/test_mro.py rename to tests/meta/test_mro.py diff --git a/tox.ini b/tox.ini index da1f1e858..32c6658bb 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ install_command = pip install {opts} {packages} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements-test.txt commands = - pytest tests/unit {posargs} + pytest tests/unit tests/meta {posargs} [testenv:pep8] basepython = python3 @@ -72,7 +72,7 @@ commands = python setup.py build_sphinx [testenv:cover] commands = pytest --cov --cov-report term --cov-report html \ - --cov-report xml tests/unit {posargs} + --cov-report xml tests/unit tests/meta {posargs} [coverage:run] omit = *tests*