cornellGrading 2.19.0__tar.gz → 2.20.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cornellgrading-2.19.0/cornellGrading.egg-info → cornellgrading-2.20.1}/PKG-INFO +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/__init__.py +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellGrading.py +268 -5
- cornellgrading-2.20.1/cornellGrading/utils.py +55 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1/cornellGrading.egg-info}/PKG-INFO +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/SOURCES.txt +1 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/LICENSE +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/MANIFEST.in +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/README.md +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellInterface.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellQualtrics.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/dueDatesFromCSV.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/pandocHTMLParser.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/upload_MC_questions.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/dependency_links.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/entry_points.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/requires.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/top_level.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/pyproject.toml +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/canvastex.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/matlabgrader.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/setupSelfGrading.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/setup.cfg +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.1}/setup.py +0 -0
|
@@ -5,6 +5,7 @@ import keyring
|
|
|
5
5
|
import time
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
import pytz
|
|
8
|
+
import canvasapi
|
|
8
9
|
from canvasapi import Canvas
|
|
9
10
|
from canvasapi.exceptions import InvalidAccessToken
|
|
10
11
|
import tempfile
|
|
@@ -16,6 +17,8 @@ import urllib.parse
|
|
|
16
17
|
import subprocess
|
|
17
18
|
import shutil
|
|
18
19
|
import requests
|
|
20
|
+
import uuid
|
|
21
|
+
from cornellGrading.utils import convalllatex
|
|
19
22
|
|
|
20
23
|
try:
|
|
21
24
|
from cornellGrading.pandocHTMLParser import pandocHTMLParser
|
|
@@ -2019,8 +2022,8 @@ class cornellGrading:
|
|
|
2019
2022
|
r = requests.post(full_url, json={"item": item}, headers=headers)
|
|
2020
2023
|
assert r.status_code == 200
|
|
2021
2024
|
|
|
2022
|
-
def
|
|
2023
|
-
"""
|
|
2025
|
+
def genHomeworkName(self, assignmentNum, preamble=""):
|
|
2026
|
+
"""Synthesize assignment name based on number and optional preamble
|
|
2024
2027
|
|
|
2025
2028
|
Args:
|
|
2026
2029
|
assignmentNum (int):
|
|
@@ -2045,6 +2048,31 @@ class cornellGrading:
|
|
|
2045
2048
|
|
|
2046
2049
|
# generate the name and grab the assignment
|
|
2047
2050
|
hwname = f"{preamble}HW{assignmentNum}"
|
|
2051
|
+
|
|
2052
|
+
return hwname
|
|
2053
|
+
|
|
2054
|
+
def getHomework(self, assignmentNum, preamble=""):
|
|
2055
|
+
"""Retrieve assignment based on number and optional preamble
|
|
2056
|
+
|
|
2057
|
+
Args:
|
|
2058
|
+
assignmentNum (int):
|
|
2059
|
+
Assignment names will be of the form "[preamble] HW#"
|
|
2060
|
+
where # is `assignmentNum`. See also `preamble` input.
|
|
2061
|
+
|
|
2062
|
+
preamble (str):
|
|
2063
|
+
If not "", then assignment name will be "[preamble] HW#". Otherwise,
|
|
2064
|
+
name will be "HW#". Defaults to ""
|
|
2065
|
+
|
|
2066
|
+
Returns:
|
|
2067
|
+
tuple:
|
|
2068
|
+
hwname (str):
|
|
2069
|
+
Full assignment name
|
|
2070
|
+
hw (canvasapi.assignment.Assignment):
|
|
2071
|
+
Assignment object
|
|
2072
|
+
"""
|
|
2073
|
+
|
|
2074
|
+
# get the name and grab the assignment
|
|
2075
|
+
hwname = self.genHomeworkName(assignmentNum, preamble=preamble)
|
|
2048
2076
|
hw = self.getAssignment(hwname)
|
|
2049
2077
|
|
|
2050
2078
|
return hwname, hw
|
|
@@ -2054,7 +2082,7 @@ class cornellGrading:
|
|
|
2054
2082
|
assignmentNum,
|
|
2055
2083
|
nprobs,
|
|
2056
2084
|
solfile,
|
|
2057
|
-
|
|
2085
|
+
npoints=3,
|
|
2058
2086
|
preamble="",
|
|
2059
2087
|
selfGradeDueDelta=7,
|
|
2060
2088
|
selfGradeReleasedDelta=3,
|
|
@@ -2070,8 +2098,8 @@ class cornellGrading:
|
|
|
2070
2098
|
Number of howmework problems
|
|
2071
2099
|
solfile (str):
|
|
2072
2100
|
Full path to solutions file to upload
|
|
2073
|
-
|
|
2074
|
-
|
|
2101
|
+
npoints (int):
|
|
2102
|
+
Number of points each question is scored out of. Defaults to 3.
|
|
2075
2103
|
preamble (str):
|
|
2076
2104
|
Preamble for all naming.
|
|
2077
2105
|
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
@@ -2132,6 +2160,9 @@ class cornellGrading:
|
|
|
2132
2160
|
}
|
|
2133
2161
|
)
|
|
2134
2162
|
|
|
2163
|
+
# create item
|
|
2164
|
+
item = self.genNpointNewQuizItem(npoints)
|
|
2165
|
+
|
|
2135
2166
|
# add new quiz items
|
|
2136
2167
|
for j in range(nprobs):
|
|
2137
2168
|
tmp = item.copy()
|
|
@@ -2256,3 +2287,235 @@ class cornellGrading:
|
|
|
2256
2287
|
self.uploadScores(hw, netids, scores)
|
|
2257
2288
|
|
|
2258
2289
|
return submittedScoreNoAssignment, submittedAssignmentNoScore
|
|
2290
|
+
|
|
2291
|
+
def genNpointNewQuizItem(self, n, item_body=None, title=None, position=0):
|
|
2292
|
+
"""Generate a New Quiz multiple choice, variable point question with answers
|
|
2293
|
+
ranging from 0 to n and each answer worth the equivalent number of points.
|
|
2294
|
+
|
|
2295
|
+
Args:
|
|
2296
|
+
n (int):
|
|
2297
|
+
Number of points possible. Question will have n+1 options (from 0 to n)
|
|
2298
|
+
with each response worth the equivalent number of points
|
|
2299
|
+
item_body (str, optional):
|
|
2300
|
+
Question text (html formatted). If None (default) this is set to
|
|
2301
|
+
<p>Enter your score based on the rubric in the syllabus</p>
|
|
2302
|
+
title (str, optional):
|
|
2303
|
+
Question title. If None (default) this is set to:
|
|
2304
|
+
HW Problem Score
|
|
2305
|
+
position (int):
|
|
2306
|
+
Position of question in quiz. Defaults to 0
|
|
2307
|
+
|
|
2308
|
+
Returns:
|
|
2309
|
+
dict:
|
|
2310
|
+
New Quiz Multiple choice question definition
|
|
2311
|
+
|
|
2312
|
+
"""
|
|
2313
|
+
|
|
2314
|
+
# set default body text and title
|
|
2315
|
+
if item_body is None:
|
|
2316
|
+
item_body = "<p>Enter your score based on the rubric in the syllabus</p>"
|
|
2317
|
+
|
|
2318
|
+
if title is None:
|
|
2319
|
+
title = "HW Problem Score"
|
|
2320
|
+
|
|
2321
|
+
# generate UUIDs
|
|
2322
|
+
uuids = [uuid.uuid4() for _ in range(n + 1)]
|
|
2323
|
+
|
|
2324
|
+
# create choices and values dict list
|
|
2325
|
+
choices = []
|
|
2326
|
+
values = []
|
|
2327
|
+
for j in range(n + 1):
|
|
2328
|
+
choices.append(
|
|
2329
|
+
{"id": f"{uuids[j]}", "position": j + 1, "item_body": f"<p>{j}</p>"}
|
|
2330
|
+
)
|
|
2331
|
+
values.append({"value": f"{uuids[j]}", "points": j})
|
|
2332
|
+
|
|
2333
|
+
q = {
|
|
2334
|
+
"position": position,
|
|
2335
|
+
"points_possible": float(n),
|
|
2336
|
+
"entry_type": "Item",
|
|
2337
|
+
"status": "immutable",
|
|
2338
|
+
"entry": {
|
|
2339
|
+
"title": title,
|
|
2340
|
+
"item_body": item_body,
|
|
2341
|
+
"calculator_type": "none",
|
|
2342
|
+
"interaction_data": {"choices": choices},
|
|
2343
|
+
"properties": {
|
|
2344
|
+
"shuffle_rules": {"choices": {"to_lock": [], "shuffled": False}},
|
|
2345
|
+
"vary_points_by_answer": True,
|
|
2346
|
+
},
|
|
2347
|
+
"scoring_data": {
|
|
2348
|
+
"value": f"{uuids[-1]}",
|
|
2349
|
+
"values": values,
|
|
2350
|
+
},
|
|
2351
|
+
"answer_feedback": {f"{uuids[0]}": ""},
|
|
2352
|
+
"scoring_algorithm": "VaryPointsByAnswer",
|
|
2353
|
+
"interaction_type_slug": "choice",
|
|
2354
|
+
"feedback": {},
|
|
2355
|
+
},
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
return q
|
|
2359
|
+
|
|
2360
|
+
def genNewQuizMultipleChoice(
|
|
2361
|
+
self, question, options, correct_ind, points=1, position=0
|
|
2362
|
+
):
|
|
2363
|
+
"""Generate a new quiz-style multiple choice question dictionary
|
|
2364
|
+
|
|
2365
|
+
Args:
|
|
2366
|
+
question (str):
|
|
2367
|
+
The raw question text (may include LaTeX)
|
|
2368
|
+
options (list(str) or ~numpy.ndarray(str)):
|
|
2369
|
+
Possible answers. All contents must be strings (may include LaTeX)
|
|
2370
|
+
correct_ind (ind):
|
|
2371
|
+
Index of correct answer in `options` input.
|
|
2372
|
+
points (int):
|
|
2373
|
+
Number of points for correct answer (defaults to 1).
|
|
2374
|
+
position (int):
|
|
2375
|
+
Position of question in quiz. Defaults to 0
|
|
2376
|
+
|
|
2377
|
+
Returns:
|
|
2378
|
+
dict:
|
|
2379
|
+
Question-defining dictionary
|
|
2380
|
+
|
|
2381
|
+
"""
|
|
2382
|
+
|
|
2383
|
+
# generate UUIDs
|
|
2384
|
+
uuids = [uuid.uuid4() for _ in range(len(options))]
|
|
2385
|
+
|
|
2386
|
+
# create choices dict list
|
|
2387
|
+
choices = []
|
|
2388
|
+
for j in range(len(options)):
|
|
2389
|
+
choices.append(
|
|
2390
|
+
{
|
|
2391
|
+
"id": f"{uuids[j]}",
|
|
2392
|
+
"position": j + 1,
|
|
2393
|
+
"item_body": f"<p>{convalllatex(options[j])}</p>",
|
|
2394
|
+
}
|
|
2395
|
+
)
|
|
2396
|
+
|
|
2397
|
+
q = {
|
|
2398
|
+
"position": position,
|
|
2399
|
+
"points_possible": float(points),
|
|
2400
|
+
"properties": {},
|
|
2401
|
+
"entry_type": "Item",
|
|
2402
|
+
"entry_editable": True,
|
|
2403
|
+
"stimulus_quiz_entry_id": "",
|
|
2404
|
+
"status": "mutable",
|
|
2405
|
+
"entry": {
|
|
2406
|
+
"title": question,
|
|
2407
|
+
"item_body": f"<p>{convalllatex(question)}</p>",
|
|
2408
|
+
"calculator_type": "none",
|
|
2409
|
+
"interaction_data": {"choices": choices},
|
|
2410
|
+
"properties": {
|
|
2411
|
+
"shuffle_rules": {"choices": {"to_lock": [], "shuffled": False}},
|
|
2412
|
+
"vary_points_by_answer": False,
|
|
2413
|
+
},
|
|
2414
|
+
"scoring_data": {"value": f"{uuids[correct_ind]}"},
|
|
2415
|
+
"answer_feedback": {},
|
|
2416
|
+
"scoring_algorithm": "Equivalence",
|
|
2417
|
+
"interaction_type_slug": "choice",
|
|
2418
|
+
"feedback": {},
|
|
2419
|
+
},
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
return q
|
|
2423
|
+
|
|
2424
|
+
def genQuizMultipleChoice(
|
|
2425
|
+
self, question, options, correct_ind, points=1, position=0
|
|
2426
|
+
):
|
|
2427
|
+
""" "Generate a classic quiz-style multiple choice question dictionary
|
|
2428
|
+
|
|
2429
|
+
Args:
|
|
2430
|
+
question (str):
|
|
2431
|
+
The raw question text (may include LaTeX)
|
|
2432
|
+
options (list(str) or ~numpy.ndarray(str)):
|
|
2433
|
+
Possible answers. All contents must be strings (may include LaTeX)
|
|
2434
|
+
correct_ind (ind):
|
|
2435
|
+
Index of correct answer in `options` input.
|
|
2436
|
+
points (int):
|
|
2437
|
+
Number of points for correct answer (defaults to 1).
|
|
2438
|
+
position (int):
|
|
2439
|
+
Position of question in quiz. Defaults to 0
|
|
2440
|
+
|
|
2441
|
+
Returns:
|
|
2442
|
+
dict:
|
|
2443
|
+
Question-defining dictionary
|
|
2444
|
+
|
|
2445
|
+
"""
|
|
2446
|
+
|
|
2447
|
+
# create answers dict list
|
|
2448
|
+
answers = []
|
|
2449
|
+
for j in range(len(options)):
|
|
2450
|
+
answers.append(
|
|
2451
|
+
{
|
|
2452
|
+
"answer_html": f"<p>{convalllatex(options[j])}</p>",
|
|
2453
|
+
"answer_weight": 100 if j == correct_ind else 0,
|
|
2454
|
+
}
|
|
2455
|
+
)
|
|
2456
|
+
|
|
2457
|
+
q = {
|
|
2458
|
+
"question_name": question,
|
|
2459
|
+
"question_text": f"<p>{convalllatex(question)}</p>",
|
|
2460
|
+
"question_type": "multiple_choice_question",
|
|
2461
|
+
"position": 0,
|
|
2462
|
+
"points_possible": points,
|
|
2463
|
+
"answers": answers,
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
return q
|
|
2467
|
+
|
|
2468
|
+
def genQuizFromPolev(self, allqs, quiz):
|
|
2469
|
+
"""Generate quiz items from a PollEv formatted CSV input file
|
|
2470
|
+
|
|
2471
|
+
Args:
|
|
2472
|
+
allqs (pandas.DataFrame):
|
|
2473
|
+
Table of questions and answers, formatted in PolEV CSV upload style
|
|
2474
|
+
quiz (canvasapi.new_quiz.NewQuiz or canvasapi.quiz.Quiz):
|
|
2475
|
+
Quiz object
|
|
2476
|
+
|
|
2477
|
+
Returns:
|
|
2478
|
+
None
|
|
2479
|
+
|
|
2480
|
+
.. note::
|
|
2481
|
+
Poll Everywhere upload format documentation is available here:
|
|
2482
|
+
https://support.polleverywhere.com/hc/en-us/articles/1260801546530-Import-questions
|
|
2483
|
+
|
|
2484
|
+
"""
|
|
2485
|
+
|
|
2486
|
+
assert isinstance(
|
|
2487
|
+
quiz, (canvasapi.new_quiz.NewQuiz, canvasapi.quiz.Quiz)
|
|
2488
|
+
), "quiz input must be a Quiz or New Quiz object."
|
|
2489
|
+
|
|
2490
|
+
# identify response columns:
|
|
2491
|
+
optcols = []
|
|
2492
|
+
for col in allqs.columns:
|
|
2493
|
+
if col.startswith("Option"):
|
|
2494
|
+
optcols.append(col)
|
|
2495
|
+
|
|
2496
|
+
# iterate through rows, creating the questions
|
|
2497
|
+
for j, row in allqs.iterrows():
|
|
2498
|
+
question = row.Title
|
|
2499
|
+
opts = row[optcols].values
|
|
2500
|
+
opts = opts[~pandas.isna(opts)]
|
|
2501
|
+
correct_ind = [
|
|
2502
|
+
j
|
|
2503
|
+
for j, s in enumerate(opts)
|
|
2504
|
+
if isinstance(s, str) and s.startswith("***")
|
|
2505
|
+
]
|
|
2506
|
+
assert len(correct_ind) == 1
|
|
2507
|
+
correct_ind = correct_ind[0]
|
|
2508
|
+
opts[correct_ind] = opts[correct_ind].strip("***")
|
|
2509
|
+
|
|
2510
|
+
# create and add the question depending on quiz type
|
|
2511
|
+
if isinstance(quiz, canvasapi.new_quiz.NewQuiz):
|
|
2512
|
+
q = self.genNewQuizMultipleChoice(
|
|
2513
|
+
question, opts.astype(str), correct_ind, position=j + 1
|
|
2514
|
+
)
|
|
2515
|
+
|
|
2516
|
+
self.addNewQuizItem(quiz.id, q)
|
|
2517
|
+
else:
|
|
2518
|
+
q = self.genQuizMultipleChoice(
|
|
2519
|
+
question, opts.astype(str), correct_ind, position=j + 1
|
|
2520
|
+
)
|
|
2521
|
+
quiz.create_question(question=q)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def convalllatex(text):
|
|
6
|
+
"""Convert all instances of LaTeX in a string to Canvas-compatible images
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
text (str):
|
|
10
|
+
Input text
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
str:
|
|
14
|
+
Output text
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
p = re.compile(r"\${1,2}(.*?)\${1,2}")
|
|
19
|
+
|
|
20
|
+
return p.sub(convlatex, text)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def convlatex(texstr):
|
|
24
|
+
"""Convert input latex string to Canvas's img html
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
texstr (str or re.Match):
|
|
28
|
+
LaTeX-formatted equation string
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str:
|
|
32
|
+
Canvas-style image string
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# handle case where input is re.Match
|
|
37
|
+
if isinstance(texstr, re.Match):
|
|
38
|
+
texstr = texstr.groups()[0]
|
|
39
|
+
|
|
40
|
+
# replace problematic commands int LaTeX
|
|
41
|
+
texsubdict = {
|
|
42
|
+
r"\\textrm": "",
|
|
43
|
+
}
|
|
44
|
+
for key, val in texsubdict.items():
|
|
45
|
+
texstr = re.sub(key, val, texstr)
|
|
46
|
+
|
|
47
|
+
convstr = urllib.parse.quote(urllib.parse.quote(texstr))
|
|
48
|
+
qtxt = (
|
|
49
|
+
f"""<img class="equation_image" title="{texstr}" """
|
|
50
|
+
f"""src="/equation_images/{convstr}?scale=1" """
|
|
51
|
+
f"""alt="LaTeX: {texstr}" """
|
|
52
|
+
f""" data-equation-content="{texstr}" data-ignore-a11y-check="">"""
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return qtxt
|
|
@@ -11,6 +11,7 @@ cornellGrading/cornellQualtrics.py
|
|
|
11
11
|
cornellGrading/dueDatesFromCSV.py
|
|
12
12
|
cornellGrading/pandocHTMLParser.py
|
|
13
13
|
cornellGrading/upload_MC_questions.py
|
|
14
|
+
cornellGrading/utils.py
|
|
14
15
|
cornellGrading.egg-info/PKG-INFO
|
|
15
16
|
cornellGrading.egg-info/SOURCES.txt
|
|
16
17
|
cornellGrading.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|