diff --git a/api/canvas_classes.py b/api/canvas_classes.py index 1963bdeff6dc70dcbed370110c8fbfd7ff13a77b..c05ab63ecfb05140310f5657030ca62feadcffcf 100644 --- a/api/canvas_classes.py +++ b/api/canvas_classes.py @@ -1,16 +1,18 @@ +from typing import ClassVar, Dict, Iterable, List, Union + from canvasapi import Canvas from canvasapi.assignment import Assignment, AssignmentGroup +from canvasapi.course import Course from canvasapi.group import Group, GroupCategory from canvasapi.quiz import QuizSubmission, QuizSubmissionQuestion from canvasapi.submission import Submission from canvasapi.user import User -from config import Config -from typing import Iterable, List, Union, Dict +from config import Config class CanvasSession: - __instance = None + __instance: ClassVar[Canvas] = None @staticmethod def get_session() -> Canvas: @@ -23,6 +25,8 @@ class CanvasSession: class CanvasUser: + canvas_user: User + # @overload # def __init__(self, user: int): # by NUID # super().__init__() @@ -123,6 +127,8 @@ class CanvasUser: class CanvasUserGroup: + canvas_user_group: Group + def __init__(self, group: Group): super().__init__() self.canvas_user_group = group @@ -206,6 +212,8 @@ class CanvasUserGroup: class CanvasUserGroupSet: # aka, group_category + canvas_group_category: GroupCategory + def __init__(self, group_category: GroupCategory): super().__init__() self.canvas_group_category = group_category @@ -286,6 +294,8 @@ class CanvasUserGroupSet: # aka, group_category class CanvasAssignment: + canvas_assignment: Assignment + def __init__(self, assignment: Assignment): super().__init__() self.canvas_assignment = assignment @@ -574,7 +584,8 @@ class CanvasAssignment: class CanvasAssignmentGroup: - # from canvasapi.assignment import Assignment + canvas_assignment_group: AssignmentGroup + def __init__(self, group: AssignmentGroup): super().__init__() self.canvas_assignment_group = group @@ -621,6 +632,8 @@ class CanvasAssignmentGroup: class CanvasCourse: + canvas_course: Course + def __init__(self, course_id: int): self.canvas_course = CanvasSession.get_session().get_course(course_id) diff --git a/api/gitlab_classes.py b/api/gitlab_classes.py index 9b12b76265d8a629b7909bad5e40a1ec7c0449b3..f129c66840936e87d19ed77f29d830bd2862a5c9 100644 --- a/api/gitlab_classes.py +++ b/api/gitlab_classes.py @@ -1,22 +1,26 @@ from datetime import datetime +from typing import ClassVar, Dict, Iterable, List, Set, Union -import gitlab +from gitlab import Gitlab, MAINTAINER_ACCESS +from gitlab.v4.objects import Issue, Project, ProjectCommit, User from config import Config class GitlabSession: - __instance = None + __instance: ClassVar[Gitlab] = None @staticmethod - def get_session(): + def get_session() -> Gitlab: if GitlabSession.__instance is None: - GitlabSession.__instance = gitlab.Gitlab(Config.gitlab_url, private_token=Config.gitlab_api_key) + GitlabSession.__instance = Gitlab(Config.gitlab_url, private_token=Config.gitlab_api_key) return GitlabSession.__instance class GitlabUser: - def __init__(self, user): + git_user: User + + def __init__(self, user: Union[int, str, User]): """ Creates a User object, populating the backing git_user instance with the appropriate gitlab.User object :param user: must be either a gitlab.User object, an integer representing the user ID, or a string containing @@ -30,34 +34,36 @@ class GitlabUser: else: self.git_user = user - def get_user_id(self): + def get_user_id(self) -> int: return self.git_user.id - def get_name(self): + def get_name(self) -> str: return self.git_user.name - def get_username(self): + def get_username(self) -> str: return self.git_user.username - def get_site(self): + def get_site(self) -> str: return self.git_user.web_url - def __repr__(self): + def __repr__(self) -> str: username = self.get_username() return f'@{username}' - def __eq__(self, other): - if isinstance(other, GitlabUser): - return self.get_username() == other.get_username() - else: - return False + def __eq__(self, other: "GitlabUser") -> bool: + # if isinstance(other, GitlabUser): + return self.get_username() == other.get_username() + # else: + # return False - def __ne__(self, other): + def __ne__(self, other: "GitlabUser") -> bool: return not self.__eq__(other) class GitlabIssue: - def __init__(self, issue): + git_issue: Issue + + def __init__(self, issue: Issue): """ Creates an Issue object, populating the backing git_issue instance with the appropriate gitlab.Issue object :param issue: must be a gitlab.Issue object @@ -65,60 +71,60 @@ class GitlabIssue: super().__init__() self.git_issue = issue - def get_universal_issue_id(self): + def get_universal_issue_id(self) -> int: """ :return: universally-unique identifier """ return self.git_issue.id - def get_project_issue_id(self): + def get_project_issue_id(self) -> int: """ :return: project-unique identifier """ return self.git_issue.iid - def get_title(self): + def get_title(self) -> str: """ :return: issue's title """ return self.git_issue.title - def get_description(self): + def get_description(self) -> str: """ :return: issue's description """ return self.git_issue.description - def get_state(self): + def get_state(self) -> str: """ :return: opened or closed """ return self.git_issue.state - def get_created_at(self): + def get_created_at(self) -> datetime: """ :return: an "aware" datetime object representing the creation date/time """ - created_at = self.git_issue.created_at + created_at: str = self.git_issue.created_at if created_at[-1] in ('z', 'Z'): # Didn't encounter this problem with created_at created_at = created_at[:-1] + '+00:00' return datetime.fromisoformat(created_at) - def get_updated_at(self): + def get_updated_at(self) -> datetime: """ :return: an "aware" datetime object representing the last date/time the issue was updated """ - updated_at = self.git_issue.updated_at + updated_at: str = self.git_issue.updated_at if updated_at[-1] in ('z', 'Z'): # Didn't encounter this problem with updated_at updated_at = updated_at[:-1] + '+00:00' return datetime.fromisoformat(updated_at) - def get_closed_at(self): + def get_closed_at(self) -> Union[datetime, None]: """ :return: an "aware" datetime object representing the last date/time the issue was closed, or None if the issue is open """ - closed_at = self.git_issue.closed_at + closed_at: str = self.git_issue.closed_at if closed_at is None: return None else: @@ -126,20 +132,20 @@ class GitlabIssue: closed_at = closed_at[:-1] + '+00:00' return datetime.fromisoformat(closed_at) - def get_labels(self): + def get_labels(self) -> Set[str]: """ :return: set of label names """ return set(self.git_issue.labels) # return self.git_issue.labels.list(all=True) - def get_page(self): + def get_page(self) -> str: """ :return: HTTPS URL to issue's page """ return self.git_issue.web_url - def __repr__(self): + def __repr__(self) -> str: issue_number = self.get_project_issue_id() title = self.get_title() return f'{issue_number}. {title}' @@ -171,34 +177,36 @@ class GitlabIssue: class GitlabCommit: + gitlab_commit: ProjectCommit + # noinspection PyShadowingNames - def __init__(self, commit): + def __init__(self, commit: ProjectCommit): super().__init__() self.gitlab_commit = commit - def get_author(self): + def get_author(self) -> Dict[str, str]: return {'name': self.gitlab_commit.author_name, 'email': self.gitlab_commit.author_email} - def get_timestamp(self): + def get_timestamp(self) -> str: # TODO: is a string (vs a datetime) really what we want? return self.gitlab_commit.created_at - def get_message(self): + def get_message(self) -> str: return self.gitlab_commit.message - def is_merge(self): + def is_merge(self) -> bool: return len(self.gitlab_commit.parent_ids) > 1 # noinspection PyShadowingNames - def get_diffs(self): - diffs = [] - gitlab_diffs = self.gitlab_commit.diff() + def get_diffs(self) -> List[Dict[str, Union[str, int]]]: + diffs: List[Dict[str, Union[str, int]]] = [] + gitlab_diffs: List[Dict[str, str]] = self.gitlab_commit.diff() for diff in gitlab_diffs: diffs.append({'file': diff['new_path'], 'text': diff['diff'], '+': diff['diff'].count('\n+'), '-': diff['diff'].count('\n-')}) return diffs # noinspection PyShadowingNames - def get_diff_size(self): + def get_diff_size(self) -> int: insertions = 0 deletions = 0 if not self.is_merge(): @@ -229,7 +237,9 @@ class GitlabCommit: class GitlabProject: - def __init__(self, project): + git_project: Project + + def __init__(self, project: Union[int, str, Project]): """ Creates a Project object, populating the backing git_project instance with the appropriate gitlab.Project object :param project: must be either a gitlab.Project object, an integer representing the project ID, or a string @@ -245,143 +255,144 @@ class GitlabProject: self.git_project = GitlabSession.get_session().projects.get(project.id) @staticmethod - def get_projects_by_group(group): + def get_projects_by_group(group: Union[int, str]) -> List["GitlabProject"]: """ :param group: must be either an integer representing the group ID, or a string containing the group's namespace :return: list of projects in the specified group """ if isinstance(group, int): # by group id - gitlab_projects = GitlabSession.get_session().groups.get(group).projects.list(all=True) + gitlab_projects: Iterable[Project] = GitlabSession.get_session().groups.get(group).projects.list(all=True) else: # isinstance(group, str): # by path gitlab_projects = GitlabSession.get_session().groups.get(group).projects.list(all=True) - projects = [] + projects: List[GitlabProject] = [] for project in gitlab_projects: projects.append(GitlabProject(project)) return projects @staticmethod - def get_projects_by_keyword(search_term): - gitlab_projects = GitlabSession.get_session().projects.list(search=search_term, all=True) - projects = [] + def get_projects_by_keyword(search_term: str) -> List["GitlabProject"]: + gitlab_projects: Iterable[Project] = GitlabSession.get_session().projects.list(search=search_term, all=True) + projects: List[GitlabProject] = [] for project in gitlab_projects: projects.append(GitlabProject(project)) return projects @staticmethod - def create_project(project_name): + def create_project(project_name: str) -> "GitlabProject": return GitlabProject(GitlabSession.get_session().projects.create({'name': project_name})) @staticmethod - def create_project_in_group(group_name, project_name): + def create_project_in_group(group_name: str, project_name: str) -> "GitlabProject": group_id = GitlabSession.get_session().groups.get(group_name).id return GitlabProject(GitlabSession.get_session().projects.create({'name': project_name, 'namespace_id': group_id})) - def get_project_id(self): + def get_project_id(self) -> int: return self.git_project.id - def get_name(self): + def get_name(self) -> str: """ :return: project name without namespace """ return self.git_project.name - def get_name_with_namespace(self): + def get_name_with_namespace(self) -> str: """ :return: project name with namespace, spaces around slashes """ return self.git_project.name_with_namespace - def get_path(self): + def get_path(self) -> str: """ :return: path without namespace (may differ from name if name has spaces) """ return self.git_project.path - def get_path_with_namespace(self): + def get_path_with_namespace(self) -> str: """ :return: path with namespace, no spaces around slashes """ return self.git_project.path_with_namespace - def get_cloning_url(self): + def get_cloning_url(self) -> str: """ :return: SSH URL to clone repository """ return self.git_project.ssh_url_to_repo - def get_site(self): + def get_site(self) -> str: """ :return: HTTPS URL to git site """ return self.git_project.web_url - def get_readme_url(self): + def get_readme_url(self) -> str: """ :return: HTTPS URL to README.md """ return self.git_project.readme_url - def get_visibility(self): + def get_visibility(self) -> str: """ :return: 'private', etc. """ return self.git_project.visibility - def get_creator(self): + def get_creator(self) -> GitlabUser: """ :return: User object backed by the gitlab.User object representing the user who created the repo """ return GitlabUser(self.git_project.creator_id) - def get_created_at(self): + def get_created_at(self) -> datetime: """ :return: an "aware" datetime object representing the creation date/time """ - created_at = self.git_project.created_at + created_at: str = self.git_project.created_at if created_at[-1] in ('z', 'Z'): created_at = created_at[:-1] + '+00:00' return datetime.fromisoformat(created_at) - def get_users(self): + def get_users(self) -> List[GitlabUser]: """ :return: List of User objects representing the project's members (not including inherited members) """ - gitlab_users = self.git_project.members.list(all=True) - users = [] + gitlab_users: Iterable[User] = self.git_project.members.list(all=True) + users: List[GitlabUser] = [] for user in gitlab_users: users.append(GitlabUser(user)) return users - def get_all_users(self): + def get_all_users(self) -> List[GitlabUser]: """ :return: List of User objects representing all of the project's members (including inherited members) """ - gitlab_users = self.git_project.members.all(all=True) - users = [] + gitlab_users: Iterable[User] = self.git_project.members.all(all=True) + users: List[GitlabUser] = [] for user in gitlab_users: users.append(GitlabUser(user)) return users - def add_user_as_maintainer(self, user): - self.git_project.members.create({'user_id': user.get_user_id(), 'access_level': gitlab.MAINTAINER_ACCESS}) + def add_user_as_maintainer(self, user: GitlabUser) -> None: + self.git_project.members.create({'user_id': user.get_user_id(), 'access_level': MAINTAINER_ACCESS}) - def get_issues(self): + def get_issues(self) -> List[GitlabIssue]: """ :return: List of Issue objects representing project's issues, sorted by creation date """ - gitlab_issues = self.git_project.issues.list(order_by='created_at', sort='asc', all=True) - issues = [] + gitlab_issues: Iterable[Issue] = self.git_project.issues.list(order_by='created_at', sort='asc', all=True) + issues: List[GitlabIssue] = [] for issue in gitlab_issues: issues.append(GitlabIssue(issue)) return issues - def create_issue(self, title, description): - gitlab_issue = self.git_project.issues.create({'title': title, 'description': description}) + def create_issue(self, title: str, description: str) -> GitlabIssue: + gitlab_issue: Issue = self.git_project.issues.create({'title': title, 'description': description}) return GitlabIssue(gitlab_issue) # noinspection PyShadowingNames - def get_commits(self, branch_name='', after_date='1970-01-01', before_date='9999-12-31'): + def get_commits(self, branch_name: str = '', after_date: str = '1970-01-01', before_date: str = '9999-12-31') -> \ + List[GitlabCommit]: """ :param branch_name: the branch to retrieve commits from; if an empty string (default) then retrieves commits from all branches <-- NO, RETRIEVES FROM DEFAULT BRANCH; WILL NEED TO FIX THAT #TODO @@ -391,7 +402,7 @@ class GitlabProject: having no latest-bound :return: List of Commit objects representing the project's commits that meet the specified constraints """ - filters = {} + filters: Dict[str, str] = {} if branch_name != '': filters['ref_name'] = branch_name if after_date != '1970-01-01': @@ -399,21 +410,21 @@ class GitlabProject: if before_date != '9999-12-31': filters['until'] = before_date if len(filters) == 0: - gitlab_commits = self.git_project.commits.list(all=True) + gitlab_commits: Iterable[ProjectCommit] = self.git_project.commits.list(all=True) else: gitlab_commits = self.git_project.commits.list(all=True, query_parameters=filters) - commits = [] + commits: List[GitlabCommit] = [] for commit in gitlab_commits: commits.append(GitlabCommit(commit)) return commits - def get_labels(self): + def get_labels(self) -> Set[str]: """ :return: set of label names """ return set(map(lambda label: label.name, self.git_project.labels.list())) - def __repr__(self): + def __repr__(self) -> str: return self.get_name_with_namespace() # other git_project fields: