summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn L. Villalovos <john@sodarock.com>2021-06-12 15:05:36 -0700
committerJohn L. Villalovos <john@sodarock.com>2021-09-07 08:19:39 -0700
commitc9b5d3bac8f7c1f779dd57653f718dd0fac4db4b (patch)
tree925700991c94a5a71797d6992b97c5b90ed74089
parentb8a47bae3342400a411fb9bf4bef3c15ba91c98e (diff)
downloadgitlab-c9b5d3bac8f7c1f779dd57653f718dd0fac4db4b.tar.gz
chore: improve type-hinting for managers
The 'managers' are dynamically created. This unfortunately means that we don't have any type-hints for them and so editors which understand type-hints won't know that they are valid attributes. * Add the type-hints for the managers we define. * Add a unit test that makes sure that the type-hints and the '_managers' attribute are kept in sync with each other. * Add unit test that makes sure specified managers in '_managers' have a name ending in 'Managers' to keep with current convention. * Make RESTObject._managers always present with a default value of None. * Fix a type-issue revealed now that mypy knows what the type is
-rw-r--r--gitlab/base.py5
-rw-r--r--gitlab/v4/cli.py5
-rw-r--r--gitlab/v4/objects/boards.py2
-rw-r--r--gitlab/v4/objects/commits.py4
-rw-r--r--gitlab/v4/objects/container_registry.py1
-rw-r--r--gitlab/v4/objects/deployments.py1
-rw-r--r--gitlab/v4/objects/discussions.py4
-rw-r--r--gitlab/v4/objects/epics.py3
-rw-r--r--gitlab/v4/objects/groups.py28
-rw-r--r--gitlab/v4/objects/issues.py8
-rw-r--r--gitlab/v4/objects/members.py2
-rw-r--r--gitlab/v4/objects/merge_requests.py12
-rw-r--r--gitlab/v4/objects/notes.py5
-rw-r--r--gitlab/v4/objects/packages.py1
-rw-r--r--gitlab/v4/objects/pipelines.py9
-rw-r--r--gitlab/v4/objects/projects.py76
-rw-r--r--gitlab/v4/objects/releases.py2
-rw-r--r--gitlab/v4/objects/runners.py1
-rw-r--r--gitlab/v4/objects/snippets.py4
-rw-r--r--gitlab/v4/objects/users.py22
-rw-r--r--tests/unit/objects/test_type_hints.py74
21 files changed, 249 insertions, 20 deletions
diff --git a/gitlab/base.py b/gitlab/base.py
index bea1901..361832e 100644
--- a/gitlab/base.py
+++ b/gitlab/base.py
@@ -49,6 +49,7 @@ class RESTObject(object):
_parent_attrs: Dict[str, Any]
_short_print_attr: Optional[str] = None
_updated_attrs: Dict[str, Any]
+ _managers: Optional[Iterable[Tuple[str, str]]] = None
manager: "RESTManager"
def __init__(self, manager: "RESTManager", attrs: Dict[str, Any]) -> None:
@@ -150,13 +151,13 @@ class RESTObject(object):
return hash(self.get_id())
def _create_managers(self) -> None:
- managers = getattr(self, "_managers", None)
- if managers is None:
+ if self._managers is None:
return
for attr, cls_name in self._managers:
cls = getattr(self._module, cls_name)
manager = cls(self.manager.gitlab, parent=self)
+ # Since we have our own __setattr__ method, we can't use setattr()
self.__dict__[attr] = manager
def _update_attrs(self, new_attrs: Dict[str, Any]) -> None:
diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py
index 2fc1986..6986552 100644
--- a/gitlab/v4/cli.py
+++ b/gitlab/v4/cli.py
@@ -99,7 +99,10 @@ class GitlabCLI(object):
def do_project_export_download(self) -> None:
try:
project = self.gl.projects.get(int(self.args["project_id"]), lazy=True)
- data = project.exports.get().download()
+ export_status = project.exports.get()
+ if TYPE_CHECKING:
+ assert export_status is not None
+ data = export_status.download()
sys.stdout.buffer.write(data)
except Exception as e:
diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py
index b517fde..ef8d040 100644
--- a/gitlab/v4/objects/boards.py
+++ b/gitlab/v4/objects/boards.py
@@ -26,6 +26,7 @@ class GroupBoardListManager(CRUDMixin, RESTManager):
class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject):
+ lists: GroupBoardListManager
_managers = (("lists", "GroupBoardListManager"),)
@@ -49,6 +50,7 @@ class ProjectBoardListManager(CRUDMixin, RESTManager):
class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject):
+ lists: ProjectBoardListManager
_managers = (("lists", "ProjectBoardListManager"),)
diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py
index 76e582b..0ddff9d 100644
--- a/gitlab/v4/objects/commits.py
+++ b/gitlab/v4/objects/commits.py
@@ -17,6 +17,10 @@ __all__ = [
class ProjectCommit(RESTObject):
_short_print_attr = "title"
+
+ comments: "ProjectCommitCommentManager"
+ discussions: ProjectCommitDiscussionManager
+ statuses: "ProjectCommitStatusManager"
_managers = (
("comments", "ProjectCommitCommentManager"),
("discussions", "ProjectCommitDiscussionManager"),
diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py
index 432cb7f..39f1602 100644
--- a/gitlab/v4/objects/container_registry.py
+++ b/gitlab/v4/objects/container_registry.py
@@ -12,6 +12,7 @@ __all__ = [
class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject):
+ tags: "ProjectRegistryTagManager"
_managers = (("tags", "ProjectRegistryTagManager"),)
diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py
index 8cf0fd9..73f9672 100644
--- a/gitlab/v4/objects/deployments.py
+++ b/gitlab/v4/objects/deployments.py
@@ -10,6 +10,7 @@ __all__ = [
class ProjectDeployment(SaveMixin, RESTObject):
+ mergerequests: ProjectDeploymentMergeRequestManager
_managers = (("mergerequests", "ProjectDeploymentMergeRequestManager"),)
diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py
index f91d8fb..19d1a06 100644
--- a/gitlab/v4/objects/discussions.py
+++ b/gitlab/v4/objects/discussions.py
@@ -21,6 +21,7 @@ __all__ = [
class ProjectCommitDiscussion(RESTObject):
+ notes: ProjectCommitDiscussionNoteManager
_managers = (("notes", "ProjectCommitDiscussionNoteManager"),)
@@ -32,6 +33,7 @@ class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
class ProjectIssueDiscussion(RESTObject):
+ notes: ProjectIssueDiscussionNoteManager
_managers = (("notes", "ProjectIssueDiscussionNoteManager"),)
@@ -43,6 +45,7 @@ class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager):
class ProjectMergeRequestDiscussion(SaveMixin, RESTObject):
+ notes: ProjectMergeRequestDiscussionNoteManager
_managers = (("notes", "ProjectMergeRequestDiscussionNoteManager"),)
@@ -59,6 +62,7 @@ class ProjectMergeRequestDiscussionManager(
class ProjectSnippetDiscussion(RESTObject):
+ notes: ProjectSnippetDiscussionNoteManager
_managers = (("notes", "ProjectSnippetDiscussionNoteManager"),)
diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py
index 4311aa7..4ee3611 100644
--- a/gitlab/v4/objects/epics.py
+++ b/gitlab/v4/objects/epics.py
@@ -23,6 +23,9 @@ __all__ = [
class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject):
_id_attr = "iid"
+
+ issues: "GroupEpicIssueManager"
+ resourcelabelevents: GroupEpicResourceLabelEventManager
_managers = (
("issues", "GroupEpicIssueManager"),
("resourcelabelevents", "GroupEpicResourceLabelEventManager"),
diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py
index ee82415..67fe91c 100644
--- a/gitlab/v4/objects/groups.py
+++ b/gitlab/v4/objects/groups.py
@@ -43,6 +43,34 @@ __all__ = [
class Group(SaveMixin, ObjectDeleteMixin, RESTObject):
_short_print_attr = "name"
+
+ accessrequests: GroupAccessRequestManager
+ audit_events: GroupAuditEventManager
+ badges: GroupBadgeManager
+ billable_members: GroupBillableMemberManager
+ boards: GroupBoardManager
+ clusters: GroupClusterManager
+ customattributes: GroupCustomAttributeManager
+ deploytokens: GroupDeployTokenManager
+ descendant_groups: "GroupDescendantGroupManager"
+ epics: GroupEpicManager
+ exports: GroupExportManager
+ hooks: GroupHookManager
+ imports: GroupImportManager
+ issues: GroupIssueManager
+ issues_statistics: GroupIssuesStatisticsManager
+ labels: GroupLabelManager
+ members: GroupMemberManager
+ members_all: GroupMemberAllManager
+ mergerequests: GroupMergeRequestManager
+ milestones: GroupMilestoneManager
+ notificationsettings: GroupNotificationSettingsManager
+ packages: GroupPackageManager
+ projects: GroupProjectManager
+ runners: GroupRunnerManager
+ subgroups: "GroupSubgroupManager"
+ variables: GroupVariableManager
+ wikis: GroupWikiManager
_managers = (
("accessrequests", "GroupAccessRequestManager"),
("audit_events", "GroupAuditEventManager"),
diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py
index c77a8d5..cc1ac3f 100644
--- a/gitlab/v4/objects/issues.py
+++ b/gitlab/v4/objects/issues.py
@@ -105,6 +105,14 @@ class ProjectIssue(
):
_short_print_attr = "title"
_id_attr = "iid"
+
+ awardemojis: ProjectIssueAwardEmojiManager
+ discussions: ProjectIssueDiscussionManager
+ links: "ProjectIssueLinkManager"
+ notes: ProjectIssueNoteManager
+ resourcelabelevents: ProjectIssueResourceLabelEventManager
+ resourcemilestoneevents: ProjectIssueResourceMilestoneEventManager
+ resourcestateevents: ProjectIssueResourceStateEventManager
_managers = (
("awardemojis", "ProjectIssueAwardEmojiManager"),
("discussions", "ProjectIssueDiscussionManager"),
diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py
index 3ff8de5..3812bcb 100644
--- a/gitlab/v4/objects/members.py
+++ b/gitlab/v4/objects/members.py
@@ -43,6 +43,8 @@ class GroupMemberManager(MemberAllMixin, CRUDMixin, RESTManager):
class GroupBillableMember(ObjectDeleteMixin, RESTObject):
_short_print_attr = "username"
+
+ memberships: "GroupBillableMemberMembershipManager"
_managers = (("memberships", "GroupBillableMemberMembershipManager"),)
diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py
index 2559207..63f2786 100644
--- a/gitlab/v4/objects/merge_requests.py
+++ b/gitlab/v4/objects/merge_requests.py
@@ -139,9 +139,19 @@ class ProjectMergeRequest(
):
_id_attr = "iid"
+ approval_rules: ProjectMergeRequestApprovalRuleManager
+ approvals: ProjectMergeRequestApprovalManager
+ awardemojis: ProjectMergeRequestAwardEmojiManager
+ diffs: "ProjectMergeRequestDiffManager"
+ discussions: ProjectMergeRequestDiscussionManager
+ notes: ProjectMergeRequestNoteManager
+ pipelines: ProjectMergeRequestPipelineManager
+ resourcelabelevents: ProjectMergeRequestResourceLabelEventManager
+ resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager
+ resourcestateevents: ProjectMergeRequestResourceStateEventManager
_managers = (
- ("approvals", "ProjectMergeRequestApprovalManager"),
("approval_rules", "ProjectMergeRequestApprovalRuleManager"),
+ ("approvals", "ProjectMergeRequestApprovalManager"),
("awardemojis", "ProjectMergeRequestAwardEmojiManager"),
("diffs", "ProjectMergeRequestDiffManager"),
("discussions", "ProjectMergeRequestDiscussionManager"),
diff --git a/gitlab/v4/objects/notes.py b/gitlab/v4/objects/notes.py
index d85fea7..b8a3215 100644
--- a/gitlab/v4/objects/notes.py
+++ b/gitlab/v4/objects/notes.py
@@ -71,6 +71,7 @@ class ProjectCommitDiscussionNoteManager(
class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ awardemojis: ProjectIssueNoteAwardEmojiManager
_managers = (("awardemojis", "ProjectIssueNoteAwardEmojiManager"),)
@@ -104,6 +105,7 @@ class ProjectIssueDiscussionNoteManager(
class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject):
+ awardemojis: ProjectMergeRequestNoteAwardEmojiManager
_managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),)
@@ -137,7 +139,8 @@ class ProjectMergeRequestDiscussionNoteManager(
class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject):
- _managers = (("awardemojis", "ProjectSnippetNoteAwardEmojiManager"),)
+ awardemojis: ProjectMergeRequestNoteAwardEmojiManager
+ _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),)
class ProjectSnippetNoteManager(CRUDMixin, RESTManager):
diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py
index 3e9d9f2..fee043a 100644
--- a/gitlab/v4/objects/packages.py
+++ b/gitlab/v4/objects/packages.py
@@ -143,6 +143,7 @@ class GroupPackageManager(ListMixin, RESTManager):
class ProjectPackage(ObjectDeleteMixin, RESTObject):
+ package_files: "ProjectPackageFileManager"
_managers = (("package_files", "ProjectPackageFileManager"),)
diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py
index 5118e78..bf41ce1 100644
--- a/gitlab/v4/objects/pipelines.py
+++ b/gitlab/v4/objects/pipelines.py
@@ -74,11 +74,15 @@ class ProjectMergeRequestPipelineManager(CreateMixin, ListMixin, RESTManager):
class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject):
+ bridges: "ProjectPipelineBridgeManager"
+ jobs: "ProjectPipelineJobManager"
+ test_report: "ProjectPipelineTestReportManager"
+ variables: "ProjectPipelineVariableManager"
_managers = (
- ("jobs", "ProjectPipelineJobManager"),
("bridges", "ProjectPipelineBridgeManager"),
- ("variables", "ProjectPipelineVariableManager"),
+ ("jobs", "ProjectPipelineJobManager"),
("test_report", "ProjectPipelineTestReportManager"),
+ ("variables", "ProjectPipelineVariableManager"),
)
@cli.register_custom_action("ProjectPipeline")
@@ -199,6 +203,7 @@ class ProjectPipelineScheduleVariableManager(
class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject):
+ variables: ProjectPipelineScheduleVariableManager
_managers = (("variables", "ProjectPipelineScheduleVariableManager"),)
@cli.register_custom_action("ProjectPipelineSchedule")
diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py
index ee7aca8..71d4564 100644
--- a/gitlab/v4/objects/projects.py
+++ b/gitlab/v4/objects/projects.py
@@ -110,29 +110,88 @@ class GroupProjectManager(ListMixin, RESTManager):
class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTObject):
_short_print_attr = "path"
+
+ access_tokens: ProjectAccessTokenManager
+ accessrequests: ProjectAccessRequestManager
+ additionalstatistics: ProjectAdditionalStatisticsManager
+ approvalrules: ProjectApprovalRuleManager
+ approvals: ProjectApprovalManager
+ audit_events: ProjectAuditEventManager
+ badges: ProjectBadgeManager
+ boards: ProjectBoardManager
+ branches: ProjectBranchManager
+ clusters: ProjectClusterManager
+ commits: ProjectCommitManager
+ customattributes: ProjectCustomAttributeManager
+ deployments: ProjectDeploymentManager
+ deploytokens: ProjectDeployTokenManager
+ environments: ProjectEnvironmentManager
+ events: ProjectEventManager
+ exports: ProjectExportManager
+ files: ProjectFileManager
+ forks: "ProjectForkManager"
+ generic_packages: GenericPackageManager
+ hooks: ProjectHookManager
+ imports: ProjectImportManager
+ issues: ProjectIssueManager
+ issues_statistics: ProjectIssuesStatisticsManager
+ issuesstatistics: ProjectIssuesStatisticsManager
+ jobs: ProjectJobManager
+ keys: ProjectKeyManager
+ labels: ProjectLabelManager
+ members: ProjectMemberManager
+ members_all: ProjectMemberAllManager
+ mergerequests: ProjectMergeRequestManager
+ milestones: ProjectMilestoneManager
+ notes: ProjectNoteManager
+ notificationsettings: ProjectNotificationSettingsManager
+ packages: ProjectPackageManager
+ pagesdomains: ProjectPagesDomainManager
+ pipelines: ProjectPipelineManager
+ pipelineschedules: ProjectPipelineScheduleManager
+ protectedbranches: ProjectProtectedBranchManager
+ protectedtags: ProjectProtectedTagManager
+ pushrules: ProjectPushRulesManager
+ releases: ProjectReleaseManager
+ remote_mirrors: "ProjectRemoteMirrorManager"
+ repositories: ProjectRegistryRepositoryManager
+ runners: ProjectRunnerManager
+ services: ProjectServiceManager
+ snippets: ProjectSnippetManager
+ tags: ProjectTagManager
+ triggers: ProjectTriggerManager
+ users: ProjectUserManager
+ variables: ProjectVariableManager
+ wikis: ProjectWikiManager
+
_managers = (
("access_tokens", "ProjectAccessTokenManager"),
("accessrequests", "ProjectAccessRequestManager"),
- ("approvals", "ProjectApprovalManager"),
+ ("additionalstatistics", "ProjectAdditionalStatisticsManager"),
("approvalrules", "ProjectApprovalRuleManager"),
+ ("approvals", "ProjectApprovalManager"),
+ ("audit_events", "ProjectAuditEventManager"),
("badges", "ProjectBadgeManager"),
("boards", "ProjectBoardManager"),
("branches", "ProjectBranchManager"),
- ("jobs", "ProjectJobManager"),
+ ("clusters", "ProjectClusterManager"),
("commits", "ProjectCommitManager"),
("customattributes", "ProjectCustomAttributeManager"),
("deployments", "ProjectDeploymentManager"),
+ ("deploytokens", "ProjectDeployTokenManager"),
("environments", "ProjectEnvironmentManager"),
("events", "ProjectEventManager"),
- ("audit_events", "ProjectAuditEventManager"),
("exports", "ProjectExportManager"),
("files", "ProjectFileManager"),
("forks", "ProjectForkManager"),
("generic_packages", "GenericPackageManager"),
("hooks", "ProjectHookManager"),
- ("keys", "ProjectKeyManager"),
("imports", "ProjectImportManager"),
("issues", "ProjectIssueManager"),
+ ("issues_statistics", "ProjectIssuesStatisticsManager"),
+ ("issuesstatistics", "ProjectIssuesStatisticsManager"), # Deprecated
+ ("jobs", "ProjectJobManager"),
+ ("keys", "ProjectKeyManager"),
("labels", "ProjectLabelManager"),
("members", "ProjectMemberManager"),
("members_all", "ProjectMemberAllManager"),
@@ -143,9 +202,9 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
("packages", "ProjectPackageManager"),
("pagesdomains", "ProjectPagesDomainManager"),
("pipelines", "ProjectPipelineManager"),
+ ("pipelineschedules", "ProjectPipelineScheduleManager"),
("protectedbranches", "ProjectProtectedBranchManager"),
("protectedtags", "ProjectProtectedTagManager"),
- ("pipelineschedules", "ProjectPipelineScheduleManager"),
("pushrules", "ProjectPushRulesManager"),
("releases", "ProjectReleaseManager"),
("remote_mirrors", "ProjectRemoteMirrorManager"),
@@ -154,15 +213,10 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
("services", "ProjectServiceManager"),
("snippets", "ProjectSnippetManager"),
("tags", "ProjectTagManager"),
- ("users", "ProjectUserManager"),
("triggers", "ProjectTriggerManager"),
+ ("users", "ProjectUserManager"),
("variables", "ProjectVariableManager"),
("wikis", "ProjectWikiManager"),
- ("clusters", "ProjectClusterManager"),
- ("additionalstatistics", "ProjectAdditionalStatisticsManager"),
- ("issues_statistics", "ProjectIssuesStatisticsManager"),
- ("issuesstatistics", "ProjectIssuesStatisticsManager"), # Deprecated
- ("deploytokens", "ProjectDeployTokenManager"),
)
@cli.register_custom_action("Project", ("forked_from_id",))
diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py
index e27052d..fb7f4f0 100644
--- a/gitlab/v4/objects/releases.py
+++ b/gitlab/v4/objects/releases.py
@@ -11,6 +11,8 @@ __all__ = [
class ProjectRelease(SaveMixin, RESTObject):
_id_attr = "tag_name"
+
+ links: "ProjectReleaseLinkManager"
_managers = (("links", "ProjectReleaseLinkManager"),)
diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py
index 8a18f9b..c9e93b8 100644
--- a/gitlab/v4/objects/runners.py
+++ b/gitlab/v4/objects/runners.py
@@ -34,6 +34,7 @@ class RunnerJobManager(ListMixin, RESTManager):
class Runner(SaveMixin, ObjectDeleteMixin, RESTObject):
+ jobs: RunnerJobManager
_managers = (("jobs", "RunnerJobManager"),)
diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py
index b893eca..161129d 100644
--- a/gitlab/v4/objects/snippets.py
+++ b/gitlab/v4/objects/snippets.py
@@ -77,6 +77,10 @@ class SnippetManager(CRUDMixin, RESTManager):
class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObject):
_url = "/projects/%(project_id)s/snippets"
_short_print_attr = "title"
+
+ awardemojis: ProjectSnippetAwardEmojiManager
+ discussions: ProjectSnippetDiscussionManager
+ notes: ProjectSnippetNoteManager
_managers = (
("awardemojis", "ProjectSnippetAwardEmojiManager"),
("discussions", "ProjectSnippetDiscussionManager"),
diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py
index cc5cfd8..ad907df 100644
--- a/gitlab/v4/objects/users.py
+++ b/gitlab/v4/objects/users.py
@@ -97,11 +97,16 @@ class CurrentUserStatusManager(GetWithoutIdMixin, UpdateMixin, RESTManager):
class CurrentUser(RESTObject):
_id_attr = None
_short_print_attr = "username"
+
+ emails: CurrentUserEmailManager
+ gpgkeys: CurrentUserGPGKeyManager
+ keys: CurrentUserKeyManager
+ status: CurrentUserStatusManager
_managers = (
- ("status", "CurrentUserStatusManager"),
("emails", "CurrentUserEmailManager"),
("gpgkeys", "CurrentUserGPGKeyManager"),
("keys", "CurrentUserKeyManager"),
+ ("status", "CurrentUserStatusManager"),
)
@@ -112,12 +117,25 @@ class CurrentUserManager(GetWithoutIdMixin, RESTManager):
class User(SaveMixin, ObjectDeleteMixin, RESTObject):
_short_print_attr = "username"
+
+ customattributes: UserCustomAttributeManager
+ emails: "UserEmailManager"
+ events: UserEventManager
+ followers_users: "UserFollowersManager"
+ following_users: "UserFollowingManager"
+ gpgkeys: "UserGPGKeyManager"
+ identityproviders: "UserIdentityProviderManager"
+ impersonationtokens: "UserImpersonationTokenManager"
+ keys: "UserKeyManager"
+ memberships: "UserMembershipManager"
+ projects: "UserProjectManager"
+ status: "UserStatusManager"
_managers = (
("customattributes", "UserCustomAttributeManager"),
("emails", "UserEmailManager"),
+ ("events", "UserEventManager"),
("followers_users", "UserFollowersManager"),
("following_users", "UserFollowingManager"),
- ("events", "UserEventManager"),
("gpgkeys", "UserGPGKeyManager"),
("identityproviders", "UserIdentityProviderManager"),
("impersonationtokens", "UserImpersonationTokenManager"),
diff --git a/tests/unit/objects/test_type_hints.py b/tests/unit/objects/test_type_hints.py
new file mode 100644
index 0000000..6742698
--- /dev/null
+++ b/tests/unit/objects/test_type_hints.py
@@ -0,0 +1,74 @@
+import inspect
+from typing import Dict
+
+import gitlab
+import gitlab.v4.objects
+
+
+def test_managers_annotated():
+ """Ensure _managers have been type annotated"""
+
+ failed_messages = []
+ 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 sorted(inspect.getmembers(module_value)):
+ if not inspect.isclass(class_value):
+ continue
+
+ # Ignore imported classes from gitlab.base
+ if class_value.__module__ == "gitlab.base":
+ continue
+
+ # A '_managers' attribute is only on a RESTObject
+ if not issubclass(class_value, gitlab.base.RESTObject):
+ continue
+
+ if class_value._managers is None:
+ continue
+
+ # Collect all of our annotations into a Dict[str, str]
+ annotations: Dict[str, str] = {}
+ for attr, annotation in sorted(class_value.__annotations__.items()):
+ if isinstance(annotation, type):
+ type_name = annotation.__name__
+ else:
+ type_name = annotation
+ annotations[attr] = type_name
+
+ for attr, manager_class_name in sorted(class_value._managers):
+ # All of our managers need to end with "Manager" for example
+ # "ProjectManager"
+ if not manager_class_name.endswith("Manager"):
+ failed_messages.append(
+ (
+ f"ERROR: Class: {class_name!r} for '_managers' attribute "
+ f"{attr!r} The specified manager class "
+ f"{manager_class_name!r} does not have a name ending in "
+ f"'Manager'. Manager class names are required to end in "
+ f"'Manager'"
+ )
+ )
+ continue
+ if attr not in annotations:
+ failed_messages.append(
+ (
+ f"ERROR: Class: {class_name!r}: Type annotation missing "
+ f"for '_managers' attribute {attr!r}"
+ )
+ )
+ continue
+ if manager_class_name != annotations[attr]:
+ failed_messages.append(
+ (
+ f"ERROR: Class: {class_name!r}: Type annotation mismatch "
+ f"for '_managers' attribute {attr!r}. Type annotation is "
+ f"{annotations[attr]!r} while '_managers' is "
+ f"{manager_class_name!r}"
+ )
+ )
+
+ failed_msg = "\n".join(failed_messages)
+ assert not failed_messages, failed_msg