Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
S
scripts
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
CSCE 361
scripts
Commits
d0fab743
Commit
d0fab743
authored
Jan 15, 2020
by
Christopher Bohn
Browse files
Options
Downloads
Patches
Plain Diff
Added static typing to classes that interface directly with Canvas API
parent
80ec2650
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
README.md
+9
-6
9 additions, 6 deletions
README.md
api/canvas_classes.py
+175
-153
175 additions, 153 deletions
api/canvas_classes.py
with
184 additions
and
159 deletions
README.md
+
9
−
6
View file @
d0fab743
...
...
@@ -18,9 +18,12 @@ of repositories for student assignments.
-
config.py
-
provides URLs and API keys
-
ignored (not present in remote repository)
-
config-example.py
-
sample config.py file, without API keys
-
*do not*
commit API keys to repository
-
you will need to replace
`None`
with your API keys (as strings)
-
use
`git update-index --skip-worktree config.py`
to get git to ignore all updates to config.py
-
you will need to copy to config.py and replace
`None`
with your API keys (as strings)
-
course.py
-
provides namespace and group ID
...
...
This diff is collapsed.
Click to expand it.
api/canvas_classes.py
+
175
−
153
View file @
d0fab743
from
canvasapi
import
Canvas
from
canvasapi.assignment
import
Assignment
,
AssignmentGroup
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
class
CanvasSession
:
__instance
=
None
@staticmethod
def
get_session
():
def
get_session
()
->
Canvas
:
if
CanvasSession
.
__instance
is
None
:
CanvasSession
.
__instance
=
Canvas
(
Config
.
canvas_url
,
Config
.
canvas_api_key
)
return
CanvasSession
.
__instance
...
...
@@ -16,42 +23,52 @@ class CanvasSession:
class
CanvasUser
:
def
__init__
(
self
,
user
):
# @overload
# def __init__(self, user: int): # by NUID
# super().__init__()
# self.canvas_user = CanvasSession.get_session().get_user(user, 'sis_user_id')
#
# @overload
# def __init__(self, user: User): # by existing gitlab user
# super().__init__()
# self.canvas_user = user
#
def
__init__
(
self
,
user
:
Union
[
int
,
User
]):
super
().
__init__
()
if
isinstance
(
user
,
int
):
# by NUID
self
.
canvas_user
=
CanvasSession
.
get_session
().
get_user
(
user
,
'
sis_user_id
'
)
else
:
self
.
canvas_user
=
user
def
get_name
(
self
):
def
get_name
(
self
)
->
str
:
return
self
.
canvas_user
.
name
def
get_sortable_name
(
self
):
def
get_sortable_name
(
self
)
->
str
:
return
self
.
canvas_user
.
sortable_name
def
get_username
(
self
):
def
get_username
(
self
)
->
str
:
return
self
.
canvas_user
.
login_id
def
get_canvas_id
(
self
):
def
get_canvas_id
(
self
)
->
int
:
return
self
.
canvas_user
.
id
def
get_nuid
(
self
):
return
self
.
canvas_user
.
sis_user_id
def
get_nuid
(
self
)
->
int
:
return
int
(
self
.
canvas_user
.
sis_user_id
)
def
get_email
(
self
):
def
get_email
(
self
)
->
str
:
return
self
.
canvas_user
.
email
def
__repr__
(
self
):
def
__repr__
(
self
)
->
str
:
username
=
self
.
get_username
()
return
f
'
{
username
}
'
def
__eq__
(
self
,
other
)
:
if
isinstance
(
other
,
CanvasUser
):
def
__eq__
(
self
,
other
:
"
CanvasUser
"
)
->
bool
:
#
if isinstance(other, CanvasUser):
return
self
.
get_username
()
==
other
.
get_username
()
else
:
return
False
#
else:
#
return False
def
__ne__
(
self
,
other
)
:
def
__ne__
(
self
,
other
:
"
CanvasUser
"
)
->
bool
:
return
not
self
.
__eq__
(
other
)
...
...
@@ -105,104 +122,28 @@ class CanvasUser:
"""
class
CanvasUserGroupSet
:
# aka, group_category
def
__init__
(
self
,
group_category
):
super
().
__init__
()
self
.
canvas_group_category
=
group_category
def
get_name
(
self
):
return
self
.
canvas_group_category
.
name
def
get_groups
(
self
):
canvas_groups
=
self
.
canvas_group_category
.
get_groups
()
groups
=
[]
for
group
in
canvas_groups
:
groups
.
append
(
CanvasUserGroup
(
group
))
return
groups
def
create_group
(
self
,
group_name
):
canvas_group
=
self
.
canvas_group_category
.
create_group
(
name
=
group_name
)
return
CanvasUserGroup
(
canvas_group
)
def
create_groups
(
self
,
number_of_groups
):
base_name
=
self
.
get_name
()
groups
=
[]
for
group_number
in
range
(
1
,
number_of_groups
+
1
):
canvas_group
=
self
.
create_group
(
f
'
{
base_name
}
{
group_number
}
'
)
groups
.
append
(
CanvasUserGroup
(
canvas_group
))
return
groups
def
__repr__
(
self
):
return
self
.
get_name
()
"""
{
// The ID of the group category.
"
id
"
: 17,
// The display name of the group category.
"
name
"
:
"
Math Groups
"
,
// Certain types of group categories have special role designations. Currently,
// these include:
'
communities
'
,
'
student_organized
'
, and
'
imported
'
. Regular
// course/account group categories have a role of null.
"
role
"
:
"
communities
"
,
// If the group category allows users to join a group themselves, thought they
// may only be a member of one group per group category at a time. Values
// include
'
restricted
'
,
'
enabled
'
, and null
'
enabled
'
allows students to assign
// themselves to a group
'
restricted
'
restricts them to only joining a group in
// their section null disallows students from joining groups
"
self_signup
"
: null,
// Gives instructors the ability to automatically have group leaders assigned.
// Values include
'
random
'
,
'
first
'
, and null;
'
random
'
picks a student from the
// group at random as the leader,
'
first
'
sets the first student to be assigned
// to the group as the leader
"
auto_leader
"
: null,
// The course or account that the category group belongs to. The pattern here is
// that whatever the context_type is, there will be an _id field named after
// that type. So if instead context_type was
'
Course
'
, the course_id field would
// be replaced by an course_id field.
"
context_type
"
:
"
Account
"
,
"
account_id
"
: 3,
// If self-signup is enabled, group_limit can be set to cap the number of users
// in each group. If null, there is no limit.
"
group_limit
"
: null,
// The SIS identifier for the group category. This field is only included if the
// user has permission to manage or view SIS information.
"
sis_group_category_id
"
: null,
// The unique identifier for the SIS import. This field is only included if the
// user has permission to manage SIS information.
"
sis_import_id
"
: null,
// If the group category has not yet finished a randomly student assignment
// request, a progress object will be attached, which will contain information
// related to the progress of the assignment request. Refer to the Progress API
// for more information
"
progress
"
: null
}
"""
class
CanvasUserGroup
:
def
__init__
(
self
,
group
):
def
__init__
(
self
,
group
:
Group
):
super
().
__init__
()
self
.
canvas_user_group
=
group
def
get_name
(
self
):
def
get_name
(
self
)
->
str
:
return
self
.
canvas_user_group
.
name
def
get_number_of_students
(
self
):
def
get_number_of_students
(
self
)
->
int
:
return
self
.
canvas_user_group
.
members_count
def
get_students
(
self
):
canvas_users
=
self
.
canvas_user_group
.
get_users
()
students
=
[]
def
get_students
(
self
)
->
List
[
CanvasUser
]
:
canvas_users
:
Iterable
[
User
]
=
self
.
canvas_user_group
.
get_users
()
students
:
List
[
CanvasUser
]
=
[]
for
student
in
canvas_users
:
students
.
append
(
CanvasUser
(
student
))
return
students
def
add_student
(
self
,
user
)
:
def
add_student
(
self
,
user
:
CanvasUser
)
->
None
:
self
.
canvas_user_group
.
create_membership
(
user
.
get_canvas_id
())
def
__repr__
(
self
):
def
__repr__
(
self
)
->
str
:
return
self
.
get_name
()
...
...
@@ -264,22 +205,99 @@ class CanvasUserGroup:
"""
class
CanvasUserGroupSet
:
# aka, group_category
def
__init__
(
self
,
group_category
:
GroupCategory
):
super
().
__init__
()
self
.
canvas_group_category
=
group_category
def
get_name
(
self
)
->
str
:
return
self
.
canvas_group_category
.
name
def
get_groups
(
self
)
->
List
[
CanvasUserGroup
]:
canvas_groups
:
Iterable
[
Group
]
=
self
.
canvas_group_category
.
get_groups
()
groups
:
List
[
CanvasUserGroup
]
=
[]
for
group
in
canvas_groups
:
groups
.
append
(
CanvasUserGroup
(
group
))
return
groups
def
create_group
(
self
,
group_name
:
str
)
->
CanvasUserGroup
:
canvas_group
:
Group
=
self
.
canvas_group_category
.
create_group
(
name
=
group_name
)
return
CanvasUserGroup
(
canvas_group
)
def
create_groups
(
self
,
number_of_groups
:
int
)
->
List
[
CanvasUserGroup
]:
base_name
:
str
=
self
.
get_name
()
groups
:
List
[
CanvasUserGroup
]
=
[]
for
group_number
in
range
(
1
,
number_of_groups
+
1
):
canvas_group
=
self
.
create_group
(
f
'
{
base_name
}
{
group_number
}
'
)
# groups.append(CanvasUserGroup(canvas_group))
groups
.
append
(
canvas_group
)
return
groups
def
__repr__
(
self
)
->
str
:
return
self
.
get_name
()
"""
{
// The ID of the group category.
"
id
"
: 17,
// The display name of the group category.
"
name
"
:
"
Math Groups
"
,
// Certain types of group categories have special role designations. Currently,
// these include:
'
communities
'
,
'
student_organized
'
, and
'
imported
'
. Regular
// course/account group categories have a role of null.
"
role
"
:
"
communities
"
,
// If the group category allows users to join a group themselves, thought they
// may only be a member of one group per group category at a time. Values
// include
'
restricted
'
,
'
enabled
'
, and null
'
enabled
'
allows students to assign
// themselves to a group
'
restricted
'
restricts them to only joining a group in
// their section null disallows students from joining groups
"
self_signup
"
: null,
// Gives instructors the ability to automatically have group leaders assigned.
// Values include
'
random
'
,
'
first
'
, and null;
'
random
'
picks a student from the
// group at random as the leader,
'
first
'
sets the first student to be assigned
// to the group as the leader
"
auto_leader
"
: null,
// The course or account that the category group belongs to. The pattern here is
// that whatever the context_type is, there will be an _id field named after
// that type. So if instead context_type was
'
Course
'
, the course_id field would
// be replaced by an course_id field.
"
context_type
"
:
"
Account
"
,
"
account_id
"
: 3,
// If self-signup is enabled, group_limit can be set to cap the number of users
// in each group. If null, there is no limit.
"
group_limit
"
: null,
// The SIS identifier for the group category. This field is only included if the
// user has permission to manage or view SIS information.
"
sis_group_category_id
"
: null,
// The unique identifier for the SIS import. This field is only included if the
// user has permission to manage SIS information.
"
sis_import_id
"
: null,
// If the group category has not yet finished a randomly student assignment
// request, a progress object will be attached, which will contain information
// related to the progress of the assignment request. Refer to the Progress API
// for more information
"
progress
"
: null
}
"""
# ASSIGNMENT-RELATED CLASSES
class
CanvasAssignment
:
def
__init__
(
self
,
assignment
):
def
__init__
(
self
,
assignment
:
Assignment
):
super
().
__init__
()
self
.
canvas_assignment
=
assignment
def
get_name
(
self
):
def
get_name
(
self
)
->
str
:
return
self
.
canvas_assignment
.
name
def
is_quiz
(
self
):
def
is_quiz
(
self
)
->
bool
:
return
'
online_quiz
'
in
self
.
canvas_assignment
.
submission_types
def
get_submission_text
(
self
,
canvas_user
)
:
submission
=
self
.
canvas_assignment
.
get_submission
(
canvas_user
.
get_canvas_id
())
def
get_submission_text
(
self
,
canvas_user
:
CanvasUser
)
->
Union
[
str
,
None
]
:
submission
:
Submission
=
self
.
canvas_assignment
.
get_submission
(
canvas_user
.
get_canvas_id
())
if
submission
.
submission_type
==
'
online_text_entry
'
:
return
submission
.
body
# TODO: what happens if there is no user submission?
elif
submission
.
submission_type
==
'
online_url
'
:
...
...
@@ -289,24 +307,26 @@ class CanvasAssignment:
else
:
return
None
def
get_quiz_response
(
self
,
canvas_user
)
:
questions_and_answers
=
[]
questions
=
[]
answers
=
[]
def
get_quiz_response
(
self
,
canvas_user
:
CanvasUser
)
->
List
[
Dict
[
str
,
str
]]
:
questions_and_answers
:
List
[
Dict
[
str
,
str
]]
=
[]
questions
:
List
[
str
]
=
[]
answers
:
List
[
str
]
=
[]
if
self
.
is_quiz
():
quiz_id
=
self
.
canvas_assignment
.
quiz_id
course_id
=
self
.
canvas_assignment
.
course_id
quiz_submissions
=
CanvasCourse
(
course_id
).
canvas_course
.
get_quiz
(
quiz_id
:
int
=
self
.
canvas_assignment
.
quiz_id
course_id
:
int
=
self
.
canvas_assignment
.
course_id
quiz_submissions
:
Iterable
[
QuizSubmission
]
=
CanvasCourse
(
course_id
).
canvas_course
.
get_quiz
(
quiz_id
).
get_submissions
()
# breaking encapsulation
candidate_submission
=
list
(
filter
(
lambda
q
:
q
.
user_id
==
canvas_user
.
get_canvas_id
(),
quiz_submissions
))
candidate_submission
:
List
[
QuizSubmission
]
=
list
(
filter
(
lambda
q
:
q
.
user_id
==
canvas_user
.
get_canvas_id
(),
quiz_submissions
))
if
len
(
candidate_submission
)
>
0
:
full_questions
=
candidate_submission
[
0
].
get_submission_questions
()
full_questions
:
List
[
QuizSubmissionQuestion
]
=
candidate_submission
[
0
].
get_submission_questions
()
for
question
in
full_questions
:
questions
.
append
(
question
.
question_text
)
submission
=
self
.
canvas_assignment
.
get_submission
(
canvas_user
.
get_canvas_id
(),
submission
:
Submission
=
self
.
canvas_assignment
.
get_submission
(
canvas_user
.
get_canvas_id
(),
include
=
[
'
submission_history
'
])
history
=
sorted
(
list
(
submission
.
submission_history
),
key
=
lambda
s
:
s
[
'
submitted_at
'
],
reverse
=
True
)
if
len
(
history
)
>
0
and
'
submission_data
'
in
history
[
0
]:
history
:
List
[
Submission
]
=
sorted
(
list
(
submission
.
submission_history
),
key
=
lambda
s
:
s
[
'
submitted_at
'
],
reverse
=
True
)
if
len
(
history
)
>
0
and
'
submission_data
'
in
history
[
0
]:
# TODO: add static typing to this section
submission_data
=
history
[
0
][
'
submission_data
'
]
for
datum
in
submission_data
:
answers
.
append
(
datum
[
'
text
'
])
...
...
@@ -319,7 +339,7 @@ class CanvasAssignment:
questions_and_answers
=
None
return
questions_and_answers
def
__repr__
(
self
):
def
__repr__
(
self
)
->
str
:
return
self
.
get_name
()
...
...
@@ -555,21 +575,22 @@ class CanvasAssignment:
class
CanvasAssignmentGroup
:
# from canvasapi.assignment import Assignment
def
__init__
(
self
,
group
):
def
__init__
(
self
,
group
:
AssignmentGroup
):
super
().
__init__
()
self
.
canvas_assignment_group
=
group
def
get_name
(
self
):
def
get_name
(
self
)
->
str
:
return
self
.
canvas_assignment_group
.
name
def
get_assignments
(
self
):
course_id
=
self
.
canvas_assignment_group
.
course_id
all_assignments
=
CanvasCourse
(
course_id
).
get_assignments
()
assignments
=
list
(
filter
(
lambda
a
:
a
.
canvas_assignment
.
assignment_group_id
==
self
.
canvas_assignment_group
.
id
,
def
get_assignments
(
self
)
->
List
[
CanvasAssignment
]:
course_id
:
int
=
self
.
canvas_assignment_group
.
course_id
all_assignments
:
List
[
CanvasAssignment
]
=
CanvasCourse
(
course_id
).
get_assignments
()
assignments
:
List
[
CanvasAssignment
]
=
list
(
filter
(
lambda
a
:
a
.
canvas_assignment
.
assignment_group_id
==
self
.
canvas_assignment_group
.
id
,
all_assignments
))
# breaking encapsulation
return
assignments
def
__repr__
(
self
):
def
__repr__
(
self
)
->
str
:
return
self
.
get_name
()
...
...
@@ -600,63 +621,64 @@ class CanvasAssignmentGroup:
class
CanvasCourse
:
def
__init__
(
self
,
course_id
):
def
__init__
(
self
,
course_id
:
int
):
self
.
canvas_course
=
CanvasSession
.
get_session
().
get_course
(
course_id
)
def
get_all_users
(
self
):
canvas_users
=
self
.
canvas_course
.
get_users
()
users
=
[]
def
get_all_users
(
self
)
->
List
[
CanvasUser
]
:
canvas_users
:
Iterable
[
User
]
=
self
.
canvas_course
.
get_users
()
users
:
List
[
CanvasUser
]
=
[]
for
user
in
canvas_users
:
users
.
append
(
CanvasUser
(
user
))
return
users
def
get_students
(
self
):
canvas_users
=
self
.
canvas_course
.
get_users
(
enrollment_type
=
[
'
student
'
])
students
=
[]
def
get_students
(
self
)
->
List
[
CanvasUser
]
:
canvas_users
:
Iterable
[
User
]
=
self
.
canvas_course
.
get_users
(
enrollment_type
=
[
'
student
'
])
students
:
List
[
CanvasUser
]
=
[]
for
user
in
canvas_users
:
students
.
append
(
CanvasUser
(
user
))
return
students
def
get_instructional_team
(
self
):
canvas_users
=
self
.
canvas_course
.
get_users
(
enrollment_type
=
[
'
teacher
'
,
'
ta
'
])
instructors
=
[]
def
get_instructional_team
(
self
)
->
List
[
CanvasUser
]
:
canvas_users
:
Iterable
[
User
]
=
self
.
canvas_course
.
get_users
(
enrollment_type
=
[
'
teacher
'
,
'
ta
'
])
instructors
:
List
[
CanvasUser
]
=
[]
for
user
in
canvas_users
:
instructors
.
append
(
CanvasUser
(
user
))
return
instructors
def
get_all_user_groups
(
self
):
canvas_groups
=
self
.
canvas_course
.
get_groups
()
groups
=
[]
def
get_all_user_groups
(
self
)
->
List
[
CanvasUserGroup
]
:
canvas_groups
:
Iterable
[
Group
]
=
self
.
canvas_course
.
get_groups
()
groups
:
List
[
CanvasUserGroup
]
=
[]
for
group
in
canvas_groups
:
groups
.
append
(
CanvasUserGroup
(
group
))
return
groups
def
get_user_groupsets
(
self
):
canvas_group_categories
=
self
.
canvas_course
.
get_group_categories
()
group_sets
=
[]
def
get_user_groupsets
(
self
)
->
List
[
CanvasUserGroupSet
]
:
canvas_group_categories
:
Iterable
[
GroupCategory
]
=
self
.
canvas_course
.
get_group_categories
()
group_sets
:
List
[
CanvasUserGroupSet
]
=
[]
for
group_category
in
canvas_group_categories
:
group_sets
.
append
(
CanvasUserGroupSet
(
group_category
))
return
group_sets
def
create_user_groupset
(
self
,
groupset_name
)
:
group_category
=
self
.
canvas_course
.
create_group_category
(
groupset_name
)
def
create_user_groupset
(
self
,
groupset_name
:
str
)
->
CanvasUserGroupSet
:
group_category
:
GroupCategory
=
self
.
canvas_course
.
create_group_category
(
groupset_name
)
return
CanvasUserGroupSet
(
group_category
)
def
get_assignment_groups
(
self
):
canvas_assignment_groups
=
self
.
canvas_course
.
get_assignment_groups
(
include
=
[
'
assignments
'
])
assignment_groups
=
[]
def
get_assignment_groups
(
self
)
->
List
[
CanvasAssignmentGroup
]:
canvas_assignment_groups
:
Iterable
[
AssignmentGroup
]
=
self
.
canvas_course
.
get_assignment_groups
(
include
=
[
'
assignments
'
])
assignment_groups
:
List
[
CanvasAssignmentGroup
]
=
[]
for
assignment_group
in
canvas_assignment_groups
:
assignment_groups
.
append
(
CanvasAssignmentGroup
(
assignment_group
))
return
assignment_groups
def
get_assignments
(
self
):
canvas_assignments
=
self
.
canvas_course
.
get_assignments
()
assignments
=
[]
def
get_assignments
(
self
)
->
List
[
CanvasAssignment
]
:
canvas_assignments
:
Iterable
[
Assignment
]
=
self
.
canvas_course
.
get_assignments
()
assignments
:
List
[
CanvasAssignment
]
=
[]
for
assignment
in
canvas_assignments
:
assignments
.
append
(
CanvasAssignment
(
assignment
))
return
assignments
def
__repr__
(
self
):
def
__repr__
(
self
)
->
str
:
return
f
'
{
self
.
canvas_course
.
course_code
}
:
{
self
.
canvas_course
.
name
}
'
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment