From c9b5d3bac8f7c1f779dd57653f718dd0fac4db4b Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Sat, 12 Jun 2021 15:05:36 -0700 Subject: [PATCH 1/3] 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 --- gitlab/base.py | 5 +- gitlab/v4/cli.py | 5 +- gitlab/v4/objects/boards.py | 2 + gitlab/v4/objects/commits.py | 4 ++ gitlab/v4/objects/container_registry.py | 1 + gitlab/v4/objects/deployments.py | 1 + gitlab/v4/objects/discussions.py | 4 ++ gitlab/v4/objects/epics.py | 3 + gitlab/v4/objects/groups.py | 28 +++++++++ gitlab/v4/objects/issues.py | 8 +++ gitlab/v4/objects/members.py | 2 + gitlab/v4/objects/merge_requests.py | 12 +++- gitlab/v4/objects/notes.py | 5 +- gitlab/v4/objects/packages.py | 1 + gitlab/v4/objects/pipelines.py | 9 ++- gitlab/v4/objects/projects.py | 76 +++++++++++++++++++++---- gitlab/v4/objects/releases.py | 2 + gitlab/v4/objects/runners.py | 1 + gitlab/v4/objects/snippets.py | 4 ++ gitlab/v4/objects/users.py | 22 ++++++- tests/unit/objects/test_type_hints.py | 74 ++++++++++++++++++++++++ 21 files changed, 249 insertions(+), 20 deletions(-) create mode 100644 tests/unit/objects/test_type_hints.py diff --git a/gitlab/base.py b/gitlab/base.py index bea1901d3..361832e59 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 @@ def __hash__(self) -> int: 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 2fc19868b..698655292 100644 --- a/gitlab/v4/cli.py +++ b/gitlab/v4/cli.py @@ -99,7 +99,10 @@ def do_custom(self) -> Any: 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 b517fde6f..ef8d0407a 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 76e582b31..0ddff9d87 100644 --- a/gitlab/v4/objects/commits.py +++ b/gitlab/v4/objects/commits.py @@ -17,6 +17,10 @@ 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 432cb7fd8..39f1602c7 100644 --- a/gitlab/v4/objects/container_registry.py +++ b/gitlab/v4/objects/container_registry.py @@ -12,6 +12,7 @@ class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): + tags: "ProjectRegistryTagManager" _managers = (("tags", "ProjectRegistryTagManager"),) diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py index 8cf0fd9c8..73f9672fb 100644 --- a/gitlab/v4/objects/deployments.py +++ b/gitlab/v4/objects/deployments.py @@ -10,6 +10,7 @@ class ProjectDeployment(SaveMixin, RESTObject): + mergerequests: ProjectDeploymentMergeRequestManager _managers = (("mergerequests", "ProjectDeploymentMergeRequestManager"),) diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py index f91d8fb65..19d1a0624 100644 --- a/gitlab/v4/objects/discussions.py +++ b/gitlab/v4/objects/discussions.py @@ -21,6 +21,7 @@ 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 4311aa773..4ee361168 100644 --- a/gitlab/v4/objects/epics.py +++ b/gitlab/v4/objects/epics.py @@ -23,6 +23,9 @@ 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 ee82415e1..67fe91c50 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -43,6 +43,34 @@ 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 c77a8d509..cc1ac3ff3 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 3ff8de54d..3812bcb54 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 255920749..63f2786c2 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 d85fea76d..b8a321550 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 3e9d9f278..fee043abc 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 5118e7831..bf41ce13f 100644 --- a/gitlab/v4/objects/pipelines.py +++ b/gitlab/v4/objects/pipelines.py @@ -74,11 +74,15 @@ def __call__(self, **kwargs): 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 ee7aca846..71d4564aa 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 e27052db9..fb7f4f08b 100644 --- a/gitlab/v4/objects/releases.py +++ b/gitlab/v4/objects/releases.py @@ -11,6 +11,8 @@ 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 8a18f9b38..c9e93b8b4 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 b893ecab2..161129d5a 100644 --- a/gitlab/v4/objects/snippets.py +++ b/gitlab/v4/objects/snippets.py @@ -77,6 +77,10 @@ def public(self, **kwargs): 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 cc5cfd89a..ad907df79 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 000000000..6742698da --- /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 From d8de4dc373dc608be6cf6ba14a2acc7efd3fa7a7 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Sun, 13 Jun 2021 14:40:46 -0700 Subject: [PATCH 2/3] chore: convert to using type-annotations for managers Convert our manager usage to be done via type annotations. Now to define a manager to be used in a RESTObject subclass can simply do: class ExampleClass(CRUDMixin, RESTObject): my_manager: MyManager Any type-annotation that annotates it to be of type *Manager (with the exception of RESTManager) will cause the manager to be created on the object. --- gitlab/base.py | 18 ++++-- gitlab/v4/objects/boards.py | 2 - gitlab/v4/objects/commits.py | 5 -- gitlab/v4/objects/container_registry.py | 1 - gitlab/v4/objects/deployments.py | 1 - gitlab/v4/objects/discussions.py | 4 -- gitlab/v4/objects/epics.py | 4 -- gitlab/v4/objects/groups.py | 29 ---------- gitlab/v4/objects/issues.py | 9 --- gitlab/v4/objects/members.py | 1 - gitlab/v4/objects/merge_requests.py | 12 ---- gitlab/v4/objects/notes.py | 3 - gitlab/v4/objects/packages.py | 1 - gitlab/v4/objects/pipelines.py | 7 --- gitlab/v4/objects/projects.py | 57 +------------------ gitlab/v4/objects/releases.py | 1 - gitlab/v4/objects/runners.py | 1 - gitlab/v4/objects/snippets.py | 5 -- gitlab/v4/objects/users.py | 20 ------- tests/unit/objects/test_type_hints.py | 74 ------------------------- tests/unit/test_base.py | 2 +- 21 files changed, 15 insertions(+), 242 deletions(-) delete mode 100644 tests/unit/objects/test_type_hints.py diff --git a/gitlab/base.py b/gitlab/base.py index 361832e59..bc96e0f27 100644 --- a/gitlab/base.py +++ b/gitlab/base.py @@ -49,7 +49,6 @@ 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: @@ -151,10 +150,19 @@ def __hash__(self) -> int: return hash(self.get_id()) def _create_managers(self) -> None: - if self._managers is None: - return - - for attr, cls_name in self._managers: + # NOTE(jlvillal): We are creating our managers by looking at the class + # annotations. If an attribute is annotated as being a *Manager type + # then we create the manager and assign it to the attribute. + for attr, annotation in sorted(self.__annotations__.items()): + if not isinstance(annotation, (type, str)): + continue + if isinstance(annotation, type): + cls_name = annotation.__name__ + else: + cls_name = annotation + # All *Manager classes are used except for the base "RESTManager" class + if cls_name == "RESTManager" or not cls_name.endswith("Manager"): + continue 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() diff --git a/gitlab/v4/objects/boards.py b/gitlab/v4/objects/boards.py index ef8d0407a..8b2959d9a 100644 --- a/gitlab/v4/objects/boards.py +++ b/gitlab/v4/objects/boards.py @@ -27,7 +27,6 @@ class GroupBoardListManager(CRUDMixin, RESTManager): class GroupBoard(SaveMixin, ObjectDeleteMixin, RESTObject): lists: GroupBoardListManager - _managers = (("lists", "GroupBoardListManager"),) class GroupBoardManager(CRUDMixin, RESTManager): @@ -51,7 +50,6 @@ class ProjectBoardListManager(CRUDMixin, RESTManager): class ProjectBoard(SaveMixin, ObjectDeleteMixin, RESTObject): lists: ProjectBoardListManager - _managers = (("lists", "ProjectBoardListManager"),) class ProjectBoardManager(CRUDMixin, RESTManager): diff --git a/gitlab/v4/objects/commits.py b/gitlab/v4/objects/commits.py index 0ddff9d87..05b55b0ce 100644 --- a/gitlab/v4/objects/commits.py +++ b/gitlab/v4/objects/commits.py @@ -21,11 +21,6 @@ class ProjectCommit(RESTObject): comments: "ProjectCommitCommentManager" discussions: ProjectCommitDiscussionManager statuses: "ProjectCommitStatusManager" - _managers = ( - ("comments", "ProjectCommitCommentManager"), - ("discussions", "ProjectCommitDiscussionManager"), - ("statuses", "ProjectCommitStatusManager"), - ) @cli.register_custom_action("ProjectCommit") @exc.on_http_error(exc.GitlabGetError) diff --git a/gitlab/v4/objects/container_registry.py b/gitlab/v4/objects/container_registry.py index 39f1602c7..8164e172d 100644 --- a/gitlab/v4/objects/container_registry.py +++ b/gitlab/v4/objects/container_registry.py @@ -13,7 +13,6 @@ class ProjectRegistryRepository(ObjectDeleteMixin, RESTObject): tags: "ProjectRegistryTagManager" - _managers = (("tags", "ProjectRegistryTagManager"),) class ProjectRegistryRepositoryManager(DeleteMixin, ListMixin, RESTManager): diff --git a/gitlab/v4/objects/deployments.py b/gitlab/v4/objects/deployments.py index 73f9672fb..11c60d157 100644 --- a/gitlab/v4/objects/deployments.py +++ b/gitlab/v4/objects/deployments.py @@ -11,7 +11,6 @@ class ProjectDeployment(SaveMixin, RESTObject): mergerequests: ProjectDeploymentMergeRequestManager - _managers = (("mergerequests", "ProjectDeploymentMergeRequestManager"),) class ProjectDeploymentManager(RetrieveMixin, CreateMixin, UpdateMixin, RESTManager): diff --git a/gitlab/v4/objects/discussions.py b/gitlab/v4/objects/discussions.py index 19d1a0624..ae7a4d59b 100644 --- a/gitlab/v4/objects/discussions.py +++ b/gitlab/v4/objects/discussions.py @@ -22,7 +22,6 @@ class ProjectCommitDiscussion(RESTObject): notes: ProjectCommitDiscussionNoteManager - _managers = (("notes", "ProjectCommitDiscussionNoteManager"),) class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): @@ -34,7 +33,6 @@ class ProjectCommitDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): class ProjectIssueDiscussion(RESTObject): notes: ProjectIssueDiscussionNoteManager - _managers = (("notes", "ProjectIssueDiscussionNoteManager"),) class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): @@ -46,7 +44,6 @@ class ProjectIssueDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): class ProjectMergeRequestDiscussion(SaveMixin, RESTObject): notes: ProjectMergeRequestDiscussionNoteManager - _managers = (("notes", "ProjectMergeRequestDiscussionNoteManager"),) class ProjectMergeRequestDiscussionManager( @@ -63,7 +60,6 @@ class ProjectMergeRequestDiscussionManager( class ProjectSnippetDiscussion(RESTObject): notes: ProjectSnippetDiscussionNoteManager - _managers = (("notes", "ProjectSnippetDiscussionNoteManager"),) class ProjectSnippetDiscussionManager(RetrieveMixin, CreateMixin, RESTManager): diff --git a/gitlab/v4/objects/epics.py b/gitlab/v4/objects/epics.py index 4ee361168..90dc6aded 100644 --- a/gitlab/v4/objects/epics.py +++ b/gitlab/v4/objects/epics.py @@ -26,10 +26,6 @@ class GroupEpic(ObjectDeleteMixin, SaveMixin, RESTObject): issues: "GroupEpicIssueManager" resourcelabelevents: GroupEpicResourceLabelEventManager - _managers = ( - ("issues", "GroupEpicIssueManager"), - ("resourcelabelevents", "GroupEpicResourceLabelEventManager"), - ) class GroupEpicManager(CRUDMixin, RESTManager): diff --git a/gitlab/v4/objects/groups.py b/gitlab/v4/objects/groups.py index 67fe91c50..7de4f8437 100644 --- a/gitlab/v4/objects/groups.py +++ b/gitlab/v4/objects/groups.py @@ -71,35 +71,6 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): subgroups: "GroupSubgroupManager" variables: GroupVariableManager wikis: GroupWikiManager - _managers = ( - ("accessrequests", "GroupAccessRequestManager"), - ("audit_events", "GroupAuditEventManager"), - ("badges", "GroupBadgeManager"), - ("billable_members", "GroupBillableMemberManager"), - ("boards", "GroupBoardManager"), - ("customattributes", "GroupCustomAttributeManager"), - ("descendant_groups", "GroupDescendantGroupManager"), - ("exports", "GroupExportManager"), - ("epics", "GroupEpicManager"), - ("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"), - ("clusters", "GroupClusterManager"), - ("deploytokens", "GroupDeployTokenManager"), - ("wikis", "GroupWikiManager"), - ) @cli.register_custom_action("Group", ("to_project_id",)) @exc.on_http_error(exc.GitlabTransferProjectError) diff --git a/gitlab/v4/objects/issues.py b/gitlab/v4/objects/issues.py index cc1ac3ff3..9272908dc 100644 --- a/gitlab/v4/objects/issues.py +++ b/gitlab/v4/objects/issues.py @@ -113,15 +113,6 @@ class ProjectIssue( resourcelabelevents: ProjectIssueResourceLabelEventManager resourcemilestoneevents: ProjectIssueResourceMilestoneEventManager resourcestateevents: ProjectIssueResourceStateEventManager - _managers = ( - ("awardemojis", "ProjectIssueAwardEmojiManager"), - ("discussions", "ProjectIssueDiscussionManager"), - ("links", "ProjectIssueLinkManager"), - ("notes", "ProjectIssueNoteManager"), - ("resourcelabelevents", "ProjectIssueResourceLabelEventManager"), - ("resourcemilestoneevents", "ProjectIssueResourceMilestoneEventManager"), - ("resourcestateevents", "ProjectIssueResourceStateEventManager"), - ) @cli.register_custom_action("ProjectIssue", ("to_project_id",)) @exc.on_http_error(exc.GitlabUpdateError) diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py index 3812bcb54..b2f4c078b 100644 --- a/gitlab/v4/objects/members.py +++ b/gitlab/v4/objects/members.py @@ -45,7 +45,6 @@ class GroupBillableMember(ObjectDeleteMixin, RESTObject): _short_print_attr = "username" memberships: "GroupBillableMemberMembershipManager" - _managers = (("memberships", "GroupBillableMemberMembershipManager"),) class GroupBillableMemberManager(ListMixin, DeleteMixin, RESTManager): diff --git a/gitlab/v4/objects/merge_requests.py b/gitlab/v4/objects/merge_requests.py index 63f2786c2..4def98c4f 100644 --- a/gitlab/v4/objects/merge_requests.py +++ b/gitlab/v4/objects/merge_requests.py @@ -149,18 +149,6 @@ class ProjectMergeRequest( resourcelabelevents: ProjectMergeRequestResourceLabelEventManager resourcemilestoneevents: ProjectMergeRequestResourceMilestoneEventManager resourcestateevents: ProjectMergeRequestResourceStateEventManager - _managers = ( - ("approval_rules", "ProjectMergeRequestApprovalRuleManager"), - ("approvals", "ProjectMergeRequestApprovalManager"), - ("awardemojis", "ProjectMergeRequestAwardEmojiManager"), - ("diffs", "ProjectMergeRequestDiffManager"), - ("discussions", "ProjectMergeRequestDiscussionManager"), - ("notes", "ProjectMergeRequestNoteManager"), - ("pipelines", "ProjectMergeRequestPipelineManager"), - ("resourcelabelevents", "ProjectMergeRequestResourceLabelEventManager"), - ("resourcemilestoneevents", "ProjectMergeRequestResourceMilestoneEventManager"), - ("resourcestateevents", "ProjectMergeRequestResourceStateEventManager"), - ) @cli.register_custom_action("ProjectMergeRequest") @exc.on_http_error(exc.GitlabMROnBuildSuccessError) diff --git a/gitlab/v4/objects/notes.py b/gitlab/v4/objects/notes.py index b8a321550..cbd237ed4 100644 --- a/gitlab/v4/objects/notes.py +++ b/gitlab/v4/objects/notes.py @@ -72,7 +72,6 @@ class ProjectCommitDiscussionNoteManager( class ProjectIssueNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectIssueNoteAwardEmojiManager - _managers = (("awardemojis", "ProjectIssueNoteAwardEmojiManager"),) class ProjectIssueNoteManager(CRUDMixin, RESTManager): @@ -106,7 +105,6 @@ class ProjectIssueDiscussionNoteManager( class ProjectMergeRequestNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectMergeRequestNoteAwardEmojiManager - _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),) class ProjectMergeRequestNoteManager(CRUDMixin, RESTManager): @@ -140,7 +138,6 @@ class ProjectMergeRequestDiscussionNoteManager( class ProjectSnippetNote(SaveMixin, ObjectDeleteMixin, RESTObject): awardemojis: ProjectMergeRequestNoteAwardEmojiManager - _managers = (("awardemojis", "ProjectMergeRequestNoteAwardEmojiManager"),) class ProjectSnippetNoteManager(CRUDMixin, RESTManager): diff --git a/gitlab/v4/objects/packages.py b/gitlab/v4/objects/packages.py index fee043abc..d7fe9dc36 100644 --- a/gitlab/v4/objects/packages.py +++ b/gitlab/v4/objects/packages.py @@ -144,7 +144,6 @@ class GroupPackageManager(ListMixin, RESTManager): class ProjectPackage(ObjectDeleteMixin, RESTObject): package_files: "ProjectPackageFileManager" - _managers = (("package_files", "ProjectPackageFileManager"),) class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager): diff --git a/gitlab/v4/objects/pipelines.py b/gitlab/v4/objects/pipelines.py index bf41ce13f..d604a3af0 100644 --- a/gitlab/v4/objects/pipelines.py +++ b/gitlab/v4/objects/pipelines.py @@ -78,12 +78,6 @@ class ProjectPipeline(RefreshMixin, ObjectDeleteMixin, RESTObject): jobs: "ProjectPipelineJobManager" test_report: "ProjectPipelineTestReportManager" variables: "ProjectPipelineVariableManager" - _managers = ( - ("bridges", "ProjectPipelineBridgeManager"), - ("jobs", "ProjectPipelineJobManager"), - ("test_report", "ProjectPipelineTestReportManager"), - ("variables", "ProjectPipelineVariableManager"), - ) @cli.register_custom_action("ProjectPipeline") @exc.on_http_error(exc.GitlabPipelineCancelError) @@ -204,7 +198,6 @@ class ProjectPipelineScheduleVariableManager( class ProjectPipelineSchedule(SaveMixin, ObjectDeleteMixin, RESTObject): variables: ProjectPipelineScheduleVariableManager - _managers = (("variables", "ProjectPipelineScheduleVariableManager"),) @cli.register_custom_action("ProjectPipelineSchedule") @exc.on_http_error(exc.GitlabOwnershipError) diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 71d4564aa..8392ddad8 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -135,7 +135,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO imports: ProjectImportManager issues: ProjectIssueManager issues_statistics: ProjectIssuesStatisticsManager - issuesstatistics: ProjectIssuesStatisticsManager + issuesstatistics: ProjectIssuesStatisticsManager # Deprecated jobs: ProjectJobManager keys: ProjectKeyManager labels: ProjectLabelManager @@ -164,61 +164,6 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO variables: ProjectVariableManager wikis: ProjectWikiManager - _managers = ( - ("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"), # Deprecated - ("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"), - ) - @cli.register_custom_action("Project", ("forked_from_id",)) @exc.on_http_error(exc.GitlabCreateError) def create_fork_relation(self, forked_from_id: int, **kwargs: Any) -> None: diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py index fb7f4f08b..2af3248db 100644 --- a/gitlab/v4/objects/releases.py +++ b/gitlab/v4/objects/releases.py @@ -13,7 +13,6 @@ class ProjectRelease(SaveMixin, RESTObject): _id_attr = "tag_name" links: "ProjectReleaseLinkManager" - _managers = (("links", "ProjectReleaseLinkManager"),) class ProjectReleaseManager(CRUDMixin, RESTManager): diff --git a/gitlab/v4/objects/runners.py b/gitlab/v4/objects/runners.py index c9e93b8b4..a32dc8493 100644 --- a/gitlab/v4/objects/runners.py +++ b/gitlab/v4/objects/runners.py @@ -35,7 +35,6 @@ class RunnerJobManager(ListMixin, RESTManager): class Runner(SaveMixin, ObjectDeleteMixin, RESTObject): jobs: RunnerJobManager - _managers = (("jobs", "RunnerJobManager"),) class RunnerManager(CRUDMixin, RESTManager): diff --git a/gitlab/v4/objects/snippets.py b/gitlab/v4/objects/snippets.py index 161129d5a..164b30cb4 100644 --- a/gitlab/v4/objects/snippets.py +++ b/gitlab/v4/objects/snippets.py @@ -81,11 +81,6 @@ class ProjectSnippet(UserAgentDetailMixin, SaveMixin, ObjectDeleteMixin, RESTObj awardemojis: ProjectSnippetAwardEmojiManager discussions: ProjectSnippetDiscussionManager notes: ProjectSnippetNoteManager - _managers = ( - ("awardemojis", "ProjectSnippetAwardEmojiManager"), - ("discussions", "ProjectSnippetDiscussionManager"), - ("notes", "ProjectSnippetNoteManager"), - ) @cli.register_custom_action("ProjectSnippet") @exc.on_http_error(exc.GitlabGetError) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index ad907df79..c0f874559 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -102,12 +102,6 @@ class CurrentUser(RESTObject): gpgkeys: CurrentUserGPGKeyManager keys: CurrentUserKeyManager status: CurrentUserStatusManager - _managers = ( - ("emails", "CurrentUserEmailManager"), - ("gpgkeys", "CurrentUserGPGKeyManager"), - ("keys", "CurrentUserKeyManager"), - ("status", "CurrentUserStatusManager"), - ) class CurrentUserManager(GetWithoutIdMixin, RESTManager): @@ -130,20 +124,6 @@ class User(SaveMixin, ObjectDeleteMixin, RESTObject): memberships: "UserMembershipManager" projects: "UserProjectManager" status: "UserStatusManager" - _managers = ( - ("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"), - ) @cli.register_custom_action("User") @exc.on_http_error(exc.GitlabBlockError) diff --git a/tests/unit/objects/test_type_hints.py b/tests/unit/objects/test_type_hints.py deleted file mode 100644 index 6742698da..000000000 --- a/tests/unit/objects/test_type_hints.py +++ /dev/null @@ -1,74 +0,0 @@ -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 diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py index 8872dbd6d..cccdfad8d 100644 --- a/tests/unit/test_base.py +++ b/tests/unit/test_base.py @@ -147,7 +147,7 @@ def test_dir_unique(self, fake_manager): def test_create_managers(self, fake_gitlab, fake_manager): class ObjectWithManager(FakeObject): - _managers = (("fakes", "FakeManager"),) + fakes: "FakeManager" obj = ObjectWithManager(fake_manager, {"foo": "bar"}) obj.id = 42 From 487b9a875a18bb3b4e0d49237bb7129d2c6dba2f Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Wed, 8 Sep 2021 08:23:25 -0700 Subject: [PATCH 3/3] chore: attempt to fix flaky functional test Add an additional check to attempt to solve the flakiness of the test_merge_request_should_remove_source_branch() test. --- tests/functional/api/test_merge_requests.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/functional/api/test_merge_requests.py b/tests/functional/api/test_merge_requests.py index 179ae6f54..b20b66a5b 100644 --- a/tests/functional/api/test_merge_requests.py +++ b/tests/functional/api/test_merge_requests.py @@ -125,10 +125,18 @@ def test_merge_request_should_remove_source_branch( time.sleep(0.5) assert mr.merged_at is not None time.sleep(0.5) + result = wait_for_sidekiq(timeout=60) + assert result is True, "sidekiq process should have terminated but did not" # Ensure we can NOT get the MR branch with pytest.raises(gitlab.exceptions.GitlabGetError): - project.branches.get(source_branch) + result = project.branches.get(source_branch) + # Help to debug in case the expected exception doesn't happen. + import pprint + + print("mr:", pprint.pformat(mr)) + print("mr.merged_at:", pprint.pformat(mr.merged_at)) + print("result:", pprint.pformat(result)) def test_merge_request_large_commit_message(