class SubmissionResponse(ChisubmitAPIObject): _api_attributes = { "submission": Attribute(name="registration", attrtype=APIObjectType(Submission), editable=False), "extensions_before": Attribute(name="extensions_before", attrtype=APIIntegerType, editable=False), "extensions_after": Attribute(name="extensions_after", attrtype=APIIntegerType, editable=False), "extensions_needed": Attribute(name="extensions_needed", attrtype=APIIntegerType, editable=False), "extensions_override": Attribute(name="extensions_override", attrtype=APIIntegerType, editable=False), "in_grace_period": Attribute(name="in_grace_period", attrtype=APIBooleanType, editable=True) } _api_relationships = { }
class Submission(ChisubmitAPIObject): _api_attributes = { "id": Attribute(name="id", attrtype=APIIntegerType, editable=False), "extensions_used": Attribute(name="extensions_used", attrtype=APIIntegerType, editable=True), "commit_sha": Attribute(name="commit_sha", attrtype=APIStringType, editable=True), "submitted_at": Attribute(name="submitted_at", attrtype=APIDateTimeType, editable=True) , "submitted_by": Attribute(name="submitted_by", attrtype=APIStringType, editable=True), "in_grace_period": Attribute(name="in_grace_period", attrtype=APIBooleanType, editable=True) } _api_relationships = { }
class Grader(ChisubmitAPIObject): _api_attributes = { "url": Attribute(name="url", attrtype=APIStringType, editable=False), "username": Attribute(name="username", attrtype=APIStringType, editable=False), "user": Attribute(name="user", attrtype=APIObjectType(User), editable=False), "git_username": Attribute(name="git_username", attrtype=APIStringType, editable=True), "git_staging_username": Attribute(name="git_staging_username", attrtype=APIStringType, editable=True) } _api_relationships = {} # TODO def get_conflicts(self): return []
class Course(ChisubmitAPIObject): _api_attributes = { "course_id": Attribute(name="course_id", attrtype=APIStringType, editable=True), "name": Attribute(name="name", attrtype=APIStringType, editable=True), "git_server_connstr": Attribute(name="git_server_connstr", attrtype=APIStringType, editable=True), "git_staging_connstr": Attribute(name="git_staging_connstr", attrtype=APIStringType, editable=True), "git_usernames": Attribute(name="git_usernames", attrtype=APIStringType, editable=True), "git_staging_usernames": Attribute(name="git_staging_usernames", attrtype=APIStringType, editable=True), "extension_policy": Attribute(name="extension_policy", attrtype=APIStringType, editable=True), "default_extensions": Attribute(name="default_extensions", attrtype=APIIntegerType, editable=True), } _api_relationships = { "instructors": Relationship(name="instructors", reltype=APIObjectType("chisubmit.client.users.Instructor")), "graders": Relationship(name="graders", reltype=APIObjectType("chisubmit.client.users.Grader")), "students": Relationship(name="students", reltype=APIObjectType("chisubmit.client.users.Student")), "assignments": Relationship(name="assignments", reltype=APIObjectType("chisubmit.client.assignment.Assignment")), "teams": Relationship(name="teams", reltype=APIObjectType("chisubmit.client.team.Team")), } def get_instructors(self): """ :calls: GET /courses/:course/instructors/ :rtype: List of :class:`chisubmit.client.users.Instructor` """ instructors = self.get_related("instructors") return instructors def get_instructor(self, username): """ :calls: GET /courses/:course/instructors/:instructor :rtype: :class:`chisubmit.client.users.Instructor` """ headers, data = self._api_client._requester.request( "GET", "/courses/" + self.course_id + "/instructors/" + username ) return chisubmit.client.users.Instructor(self._api_client, headers, data) def add_instructor(self, user_or_username, git_username = None, git_staging_username = None): """ :calls: POST /courses/:course/instructors/ :rtype: :class:`chisubmit.client.users.Instructor` """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username post_data = {"username": username} if git_username is not None: post_data["git_username"] = git_username if git_staging_username is not None: post_data["git_staging_username"] = git_staging_username headers, data = self._api_client._requester.request( "POST", "/courses/" + self.course_id + "/instructors/", data = post_data ) return chisubmit.client.users.Instructor(self._api_client, headers, data) def remove_instructor(self, user_or_username): """ :calls: DELETE /courses/:course/instructors/:username :rtype: None """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) or isinstance(user_or_username, chisubmit.client.users.Instructor) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username elif isinstance(user_or_username, chisubmit.client.users.Instructor): username = user_or_username.user.username _ = self._api_client._requester.request( "DELETE", "/courses/" + self.course_id + "/instructors/" + username ) return None def get_graders(self): """ :calls: GET /courses/:course/graders/ :rtype: List of :class:`chisubmit.client.users.Grader` """ graders = self.get_related("graders") return graders def get_grader(self, username): """ :calls: GET /courses/:course/graders/:grader :rtype: :class:`chisubmit.client.users.Grader` """ headers, data = self._api_client._requester.request( "GET", "/courses/" + self.course_id + "/graders/" + username ) return chisubmit.client.users.Grader(self._api_client, headers, data) def add_grader(self, user_or_username, git_username = None, git_staging_username = None): """ :calls: POST /courses/:course/graders/ :rtype: :class:`chisubmit.client.users.Grader` """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username post_data = {"username": username } if git_username is not None: post_data["git_username"] = git_username if git_staging_username is not None: post_data["git_staging_username"] = git_staging_username headers, data = self._api_client._requester.request( "POST", "/courses/" + self.course_id + "/graders/", data = post_data ) return chisubmit.client.users.Grader(self._api_client, headers, data) def remove_grader(self, user_or_username): """ :calls: DELETE /courses/:course/graders/:username :rtype: None """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) or isinstance(user_or_username, chisubmit.client.users.Grader) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username elif isinstance(user_or_username, chisubmit.client.users.Grader): username = user_or_username.user.username _ = self._api_client._requester.request( "DELETE", "/courses/" + self.course_id + "/graders/" + username ) return None def get_students(self): """ :calls: GET /courses/:course/students/ :rtype: List of :class:`chisubmit.client.users.Student` """ students = self.get_related("students") return students def get_student(self, username): """ :calls: GET /courses/:course/students/:grader :rtype: :class:`chisubmit.client.users.Student` """ headers, data = self._api_client._requester.request( "GET", "/courses/" + self.course_id + "/students/" + username ) return chisubmit.client.users.Student(self._api_client, headers, data) def add_student(self, user_or_username, git_username = None, extensions = None, dropped = None): """ :calls: POST /courses/:course/students/ :rtype: :class:`chisubmit.client.users.Student` """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username post_data = {"username": username } if git_username is not None: post_data["git_username"] = git_username if extensions is not None: post_data["extensions"] = extensions if dropped is not None: post_data["dropped"] = dropped headers, data = self._api_client._requester.request( "POST", "/courses/" + self.course_id + "/students/", data = post_data ) return chisubmit.client.users.Student(self._api_client, headers, data) def remove_student(self, user_or_username): """ :calls: DELETE /courses/:course/students/:username :rtype: None """ assert isinstance(user_or_username, (str, unicode)) or isinstance(user_or_username, User) or isinstance(user_or_username, chisubmit.client.users.Student) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username elif isinstance(user_or_username, chisubmit.client.users.Student): username = user_or_username.user.username _ = self._api_client._requester.request( "DELETE", "/courses/" + self.course_id + "/students/" + username ) return None def get_assignments(self, include_rubric = False): """ :calls: GET /courses/:course/assignments/ :rtype: List of :class:`chisubmit.client.assignment.Assignment` """ include = [] if include_rubric: include.append("rubric") if len(include) > 0: params = {"include": include} else: params = None assignments = self.get_related("assignments", params = params) return assignments def get_assignment(self, assignment_id, include_rubric = False): """ :calls: GET /courses/:course/assignments/:assignment/ :rtype: List of :class:`chisubmit.client.assignment.Assignment` """ include = [] if include_rubric: include.append("rubric") if len(include) > 0: params = {"include": include} else: params = None headers, data = self._api_client._requester.request( "GET", "/courses/" + self.course_id + "/assignments/" + assignment_id, params = params ) return chisubmit.client.assignment.Assignment(self._api_client, headers, data) def create_assignment(self, assignment_id, name, deadline, min_students = None, max_students = None): """ :calls: POST /courses/:course/assignments/ :param assignment_id: string :param name: string :param deadline: string :param min_students: int :param max_students: int :rtype: :class:`chisubmit.client.assignment.Assignment` """ assert isinstance(assignment_id, (str, unicode)), assignment_id assert isinstance(deadline, (str, unicode)) or isinstance(deadline, datetime.datetime), deadline # TODO: Convert/validate date if isinstance(deadline, (str, unicode)): # TODO: validate date deadline_str = deadline elif isinstance(deadline, datetime.datetime): deadline_str = deadline.isoformat(sep=" ") post_data = {"assignment_id": assignment_id, "name": name, "deadline": deadline_str} if min_students is not None: post_data["min_students"] = min_students if max_students is not None: post_data["max_students"] = max_students headers, data = self._api_client._requester.request( "POST", "/courses/" + self.course_id + "/assignments/", data = post_data ) return chisubmit.client.assignment.Assignment(self._api_client, headers, data) def get_teams(self, include_students=False, include_assignments=False, include_grades = False): """ :calls: GET /courses/:course/teams/ :rtype: List of :class:`chisubmit.client.team.Team` """ include = [] if include_students: include.append("students") if include_assignments: include.append("assignments") if include_grades: include.append("assignments__grades") if len(include) > 0: params = {"include": include} else: params = None teams = self.get_related("teams", params = params) return teams def get_team(self, team_id, include_students=False, include_assignments=False, include_grades = False): """ :calls: GET /courses/:course/teams/ :rtype: :class:`chisubmit.client.team.Team` """ assert isinstance(team_id, (str, unicode)), team_id include = [] if include_students: include.append("students") if include_assignments: include.append("assignments") if include_grades: include.append("assignments__grades") if len(include) > 0: params = {"include": include} else: params = None headers, data = self._api_client._requester.request( "GET", self.teams_url + team_id, params = params ) return chisubmit.client.team.Team(self._api_client, headers, data) def create_team(self, team_id, extensions = None, active = None): """ :calls: POST /courses/:course/teams/ :param name: string :param extensions: int :param active: bool :rtype: :class:`chisubmit.client.team.Team` """ assert isinstance(team_id, (str, unicode)), team_id post_data = { "team_id": team_id } if extensions is not None: post_data["extensions"] = extensions if active is not None: post_data["active"] = active headers, data = self._api_client._requester.request( "POST", self.teams_url, data = post_data ) return chisubmit.client.team.Team(self._api_client, headers, data)
class Team(ChisubmitAPIObject): _api_attributes = { "team_id": Attribute(name="team_id", attrtype=APIStringType, editable=True), "extensions": Attribute(name="extensions", attrtype=APIIntegerType, editable=True), "active": Attribute(name="active", attrtype=APIBooleanType, editable=True), } _api_relationships = { "students": Relationship(name="students", reltype=APIObjectType(TeamMember)), "assignments": Relationship(name="assignments", reltype=APIObjectType(Registration)), } def get_team_members(self): """ :calls: GET /courses/:course/teams/:team/students/ :rtype: List of :class:`chisubmit.client.team.TeamMember` """ team_members = self.get_related("students") return team_members def get_team_member(self, username): """ :calls: GET /courses/:course/teams/:team/students/:username :rtype: :class:`chisubmit.client.team.TeamMember` """ assert isinstance(username, (str, unicode)), username headers, data = self._api_client._requester.request( "GET", self.students_url + username) return TeamMember(self._api_client, headers, data) def add_team_member(self, user_or_username, confirmed=None): """ :calls: POST /courses/:course/teams/:team/students/ :rtype: :class:`chisubmit.client.team.TeamMember` """ assert isinstance(user_or_username, (str, unicode)) or isinstance( user_or_username, User) if isinstance(user_or_username, (str, unicode)): username = user_or_username elif isinstance(user_or_username, User): username = user_or_username.username post_data = {"username": username} if confirmed is not None: post_data["confirmed"] = confirmed headers, data = self._api_client._requester.request("POST", self.students_url, data=post_data) return TeamMember(self._api_client, headers, data) def get_assignment_registrations(self): """ :calls: GET /courses/:course/teams/:team/assignments/ :rtype: List of :class:`chisubmit.client.team.Registration` """ registrations = self.get_related("assignments") return registrations def get_assignment_registration(self, assignment_id): """ :calls: GET /courses/:course/teams/:team/assignments/:assignment :rtype: :class:`chisubmit.client.team.Registration` """ assert isinstance(assignment_id, (str, unicode)), assignment_id headers, data = self._api_client._requester.request( "GET", self.assignments_url + assignment_id) return Registration(self._api_client, headers, data) def add_assignment_registration(self, assignment_or_assignment_id, grader_or_grader_username=None): """ :calls: POST /courses/:course/teams/:team/assignments/ :rtype: :class:`chisubmit.client.team.Registration` """ assert isinstance(assignment_or_assignment_id, (str, unicode)) or isinstance( assignment_or_assignment_id, Assignment) assert grader_or_grader_username is None or isinstance( grader_or_grader_username, (str, unicode)) or isinstance(grader_or_grader_username, Grader) if isinstance(assignment_or_assignment_id, (str, unicode)): assignment_id = assignment_or_assignment_id elif isinstance(assignment_or_assignment_id, User): assignment_id = assignment_or_assignment_id.assignment_id post_data = {"assignment_id": assignment_id} if grader_or_grader_username is not None: if isinstance(grader_or_grader_username, (str, unicode)): grader_username = grader_or_grader_username elif isinstance(grader_or_grader_username, User): grader_username = grader_or_grader_username.user.username post_data["grader_username"] = grader_username headers, data = self._api_client._requester.request( "POST", self.assignments_url, data=post_data) return Registration(self._api_client, headers, data)
class Registration(ChisubmitAPIObject): _api_attributes = { "assignment_id": Attribute(name="assignment_id", attrtype=APIStringType, editable=False), "assignment": Attribute(name="assignment", attrtype=APIObjectType(Assignment), editable=False), "grader_username": Attribute(name="grader_username", attrtype=APIStringType, editable=True), "grader": Attribute(name="grader", attrtype=APIObjectType(Grader), editable=False), "final_submission_id": Attribute(name="final_submission_id", attrtype=APIIntegerType, editable=True), "final_submission": Attribute(name="final_submission", attrtype=APIObjectType(Submission), editable=False), "grade_adjustments": Attribute(name="grade_adjustments", attrtype=APIDictType(APIDecimalType), editable=True), } _api_relationships = { "submissions": Relationship(name="submissions", reltype=APIObjectType(Submission)), "grades": Relationship(name="grades", reltype=APIObjectType(Grade)), } def get_submissions(self): """ :calls: GET /courses/:course/teams/:team/assignments/:assignment/submissions :rtype: List of :class:`chisubmit.client.team.Submission` """ submissions = self.get_related("submissions") return submissions def get_submission(self, submission): """ :calls: GET /courses/:course/teams/:team/assignments/:assignment/submissions/:submission :rtype: :class:`chisubmit.client.team.Submission` """ assert isinstance(submission, int), submission headers, data = self._api_client._requester.request( "GET", self.submissions_url + str(submission)) return Submission(self._api_client, headers, data) def add_submission(self, commit_sha, extensions_used=None, submitted_at=None): """ :calls: POST /courses/:course/teams/:team/assignments/:assignment/submissions/ :rtype: :class:`chisubmit.client.team.Submission` """ post_data = {"commit_sha": commit_sha} if extensions_used is not None: post_data["extensions_used"] = extensions_used if submitted_at is not None: post_data["submitted_at"] = submitted_at headers, data = self._api_client._requester.request( "POST", self.submissions_url, data=post_data) return Submission(self._api_client, headers, data) def get_grades(self): """ :calls: GET /courses/:course/teams/:team/assignments/:assignment/grades/ :rtype: List of :class:`chisubmit.client.team.Grade` """ grades = self.get_related("grades") return grades def add_grade(self, rubric_component, points=None): """ :calls: POST /courses/:course/teams/:team/assignments/:assignment/grades/ :rtype: :class:`chisubmit.client.team.Grade` """ post_data = {"rubric_component_id": rubric_component.id} if points is not None: post_data["points"] = points headers, data = self._api_client._requester.request("POST", self.grades_url, data=post_data) return Grade(self._api_client, headers, data) def submit(self, commit_sha, extensions_override=None, dry_run=False): """ :calls: POST /courses/:course/teams/:team/assignments/:assignment/submit/ :rtype: :class:`chisubmit.client.team.Submission` """ post_data = {"commit_sha": commit_sha} if dry_run: qs = "?dry_run=true" else: qs = "" if extensions_override is not None: post_data["extensions_override"] = extensions_override headers, data = self._api_client._requester.request("POST", self.url + "/submit" + qs, data=post_data) return SubmissionResponse(self._api_client, headers, data) def cancel(self): """ :calls: DELETE /courses/:course/teams/:team/assignments/:assignment/ :rtype: None """ self.delete() def set_grade(self, rubric_component, points): if points < 0 or points > rubric_component.points: raise ValueError( "Invalid grade value %.2f ('%s' must be 0 <= x <= %.2f)" % (points, rubric_component.description, rubric_component.points)) grades = self.get_grades() grade = [ g for g in grades if g.rubric_component_id == rubric_component.id ] if len(grade) == 0: self.add_grade(rubric_component, points) elif len(grade) == 1: grade[0].points = points else: msg = "Server returned more than one grade for '%s' in %s. " % ( rubric_component.description, self.assignment.assignment_id) msg += "This should not happen. Please contact the chisubmit administrator." raise Exception(msg) def get_total_penalties(self): if self.grade_adjustments is None: return 0.0 else: return sum([v for v in self.grade_adjustments.values() if v < 0.0]) def get_total_bonuses(self): if self.grade_adjustments is None: return 0.0 else: return sum( [v for v in self.grade_adjustments.values() if v >= 0.0]) def get_total_adjustments(self): if self.grade_adjustments is None: return 0.0 else: return sum([v for v in self.grade_adjustments.values()]) def get_total_grade(self): grades = self.get_grades() return sum([g.points for g in grades]) + self.get_total_adjustments() def get_grading_branch_name(self): return self.assignment.assignment_id + "-grading" def is_ready_for_grading(self): if self.final_submission is None: return False else: return is_submission_ready_for_grading( assignment_deadline=self.assignment.deadline, submission_date=self.final_submission.submitted_at, extensions_used=self.final_submission.extensions_used)
class Assignment(ChisubmitAPIObject): _api_attributes = { "assignment_id": Attribute(name="assignment_id", attrtype=APIStringType, editable=True), "name": Attribute(name="name", attrtype=APIStringType, editable=True), "deadline": Attribute(name="deadline", attrtype=APIDateTimeType, editable=True), "grace_period": Attribute(name="grace_period", attrtype=APITimeDeltaType, editable=True), "min_students": Attribute(name="min_students", attrtype=APIIntegerType, editable=True), "max_students": Attribute(name="max_students", attrtype=APIIntegerType, editable=True), } _api_relationships = { "rubric": Relationship(name="instructors", reltype=APIObjectType(RubricComponent)), } def get_rubric_components(self): """ :calls: GET /courses/:course/assignments/:assignment/rubric :rtype: List of :class:`chisubmit.client.assignment.RubricComponent` """ rubric_components = self.get_related("rubric") return rubric_components def create_rubric_component(self, description, points, order=None): """ :calls: POST /courses/:course/assignments/:assignment/rubric/ :param description: string :param points: float :param order: int :rtype: :class:`chisubmit.client.assignment.RubricComponent` """ assert isinstance(description, (str, unicode)), description post_data = {"description": description, "points": points} if order is not None: post_data["order"] = order headers, data = self._api_client._requester.request("POST", self.rubric_url, data=post_data) return RubricComponent(self._api_client, headers, data) def register(self, students): """ :calls: POST /courses/:course/assignments/:assignment/register :param students: list of string :rtype: :class:`chisubmit.client.assignment.RegistrationResponse` """ assert isinstance(students, (list, tuple)), students post_data = {"students": students} headers, data = self._api_client._requester.request("POST", self.url + "/register", data=post_data) return RegistrationResponse(self._api_client, headers, data)