diff options
author | John L. Villalovos <john@sodarock.com> | 2021-06-12 15:05:36 -0700 |
---|---|---|
committer | John L. Villalovos <john@sodarock.com> | 2021-09-07 08:19:39 -0700 |
commit | c9b5d3bac8f7c1f779dd57653f718dd0fac4db4b (patch) | |
tree | 925700991c94a5a71797d6992b97c5b90ed74089 | |
parent | b8a47bae3342400a411fb9bf4bef3c15ba91c98e (diff) | |
download | gitlab-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.py | 5 | ||||
-rw-r--r-- | gitlab/v4/cli.py | 5 | ||||
-rw-r--r-- | gitlab/v4/objects/boards.py | 2 | ||||
-rw-r--r-- | gitlab/v4/objects/commits.py | 4 | ||||
-rw-r--r-- | gitlab/v4/objects/container_registry.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/deployments.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/discussions.py | 4 | ||||
-rw-r--r-- | gitlab/v4/objects/epics.py | 3 | ||||
-rw-r--r-- | gitlab/v4/objects/groups.py | 28 | ||||
-rw-r--r-- | gitlab/v4/objects/issues.py | 8 | ||||
-rw-r--r-- | gitlab/v4/objects/members.py | 2 | ||||
-rw-r--r-- | gitlab/v4/objects/merge_requests.py | 12 | ||||
-rw-r--r-- | gitlab/v4/objects/notes.py | 5 | ||||
-rw-r--r-- | gitlab/v4/objects/packages.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/pipelines.py | 9 | ||||
-rw-r--r-- | gitlab/v4/objects/projects.py | 76 | ||||
-rw-r--r-- | gitlab/v4/objects/releases.py | 2 | ||||
-rw-r--r-- | gitlab/v4/objects/runners.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/snippets.py | 4 | ||||
-rw-r--r-- | gitlab/v4/objects/users.py | 22 | ||||
-rw-r--r-- | tests/unit/objects/test_type_hints.py | 74 |
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 |