cornellGrading 2.19.0__tar.gz → 2.20.0__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.0}/PKG-INFO +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/__init__.py +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/cornellGrading.py +239 -3
- cornellgrading-2.20.0/cornellGrading/utils.py +55 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0/cornellGrading.egg-info}/PKG-INFO +1 -1
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading.egg-info/SOURCES.txt +1 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/LICENSE +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/MANIFEST.in +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/README.md +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/cornellInterface.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/cornellQualtrics.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/dueDatesFromCSV.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/pandocHTMLParser.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading/upload_MC_questions.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading.egg-info/dependency_links.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading.egg-info/entry_points.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading.egg-info/requires.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/cornellGrading.egg-info/top_level.txt +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/pyproject.toml +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/scripts/canvastex.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/scripts/matlabgrader.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/scripts/setupSelfGrading.py +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/setup.cfg +0 -0
- {cornellgrading-2.19.0 → cornellgrading-2.20.0}/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
|
|
@@ -2054,7 +2057,7 @@ class cornellGrading:
|
|
|
2054
2057
|
assignmentNum,
|
|
2055
2058
|
nprobs,
|
|
2056
2059
|
solfile,
|
|
2057
|
-
|
|
2060
|
+
npoints=3,
|
|
2058
2061
|
preamble="",
|
|
2059
2062
|
selfGradeDueDelta=7,
|
|
2060
2063
|
selfGradeReleasedDelta=3,
|
|
@@ -2070,8 +2073,8 @@ class cornellGrading:
|
|
|
2070
2073
|
Number of howmework problems
|
|
2071
2074
|
solfile (str):
|
|
2072
2075
|
Full path to solutions file to upload
|
|
2073
|
-
|
|
2074
|
-
|
|
2076
|
+
npoints (int):
|
|
2077
|
+
Number of points each question is scored out of. Defaults to 3.
|
|
2075
2078
|
preamble (str):
|
|
2076
2079
|
Preamble for all naming.
|
|
2077
2080
|
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
@@ -2132,6 +2135,9 @@ class cornellGrading:
|
|
|
2132
2135
|
}
|
|
2133
2136
|
)
|
|
2134
2137
|
|
|
2138
|
+
# create item
|
|
2139
|
+
item = self.genNpointNewQuizItem(npoints)
|
|
2140
|
+
|
|
2135
2141
|
# add new quiz items
|
|
2136
2142
|
for j in range(nprobs):
|
|
2137
2143
|
tmp = item.copy()
|
|
@@ -2256,3 +2262,233 @@ class cornellGrading:
|
|
|
2256
2262
|
self.uploadScores(hw, netids, scores)
|
|
2257
2263
|
|
|
2258
2264
|
return submittedScoreNoAssignment, submittedAssignmentNoScore
|
|
2265
|
+
|
|
2266
|
+
def genNpointNewQuizItem(self, n, item_body=None, title=None):
|
|
2267
|
+
"""Generate a New Quiz multiple choice, variable point question with answers
|
|
2268
|
+
ranging from 0 to n and each answer worth the equivalent number of points.
|
|
2269
|
+
|
|
2270
|
+
Args:
|
|
2271
|
+
n (int):
|
|
2272
|
+
Number of points possible. Question will have n+1 options (from 0 to n)
|
|
2273
|
+
with each response worth the equivalent number of points
|
|
2274
|
+
item_body (str, optional):
|
|
2275
|
+
Question text (html formatted). If None (default) this is set to
|
|
2276
|
+
<p>Enter your score based on the rubric in the syllabus</p>
|
|
2277
|
+
title (str, optional):
|
|
2278
|
+
Question title. If None (default) this is set to:
|
|
2279
|
+
HW Problem Score
|
|
2280
|
+
|
|
2281
|
+
Returns:
|
|
2282
|
+
dict:
|
|
2283
|
+
New Quiz Multiple choice question definition
|
|
2284
|
+
|
|
2285
|
+
"""
|
|
2286
|
+
|
|
2287
|
+
# set default body text and title
|
|
2288
|
+
if item_body is None:
|
|
2289
|
+
item_body = "<p>Enter your score based on the rubric in the syllabus</p>"
|
|
2290
|
+
|
|
2291
|
+
if title is None:
|
|
2292
|
+
title = "HW Problem Score"
|
|
2293
|
+
|
|
2294
|
+
# generate UUIDs
|
|
2295
|
+
uuids = [uuid.uuid4() for _ in range(n + 1)]
|
|
2296
|
+
|
|
2297
|
+
# create choices and values dict list
|
|
2298
|
+
choices = []
|
|
2299
|
+
values = []
|
|
2300
|
+
for j in range(n + 1):
|
|
2301
|
+
choices.append(
|
|
2302
|
+
{"id": f"{uuids[j]}", "position": j + 1, "item_body": f"<p>{j}</p>"}
|
|
2303
|
+
)
|
|
2304
|
+
values.append({"value": f"{uuids[j]}", "points": j})
|
|
2305
|
+
|
|
2306
|
+
q = {
|
|
2307
|
+
"position": position,
|
|
2308
|
+
"points_possible": float(n),
|
|
2309
|
+
"entry_type": "Item",
|
|
2310
|
+
"status": "immutable",
|
|
2311
|
+
"entry": {
|
|
2312
|
+
"title": title,
|
|
2313
|
+
"item_body": item_body,
|
|
2314
|
+
"calculator_type": "none",
|
|
2315
|
+
"interaction_data": {"choices": choices},
|
|
2316
|
+
"properties": {
|
|
2317
|
+
"shuffle_rules": {"choices": {"to_lock": [], "shuffled": False}},
|
|
2318
|
+
"vary_points_by_answer": True,
|
|
2319
|
+
},
|
|
2320
|
+
"scoring_data": {
|
|
2321
|
+
"value": f"{uuids[-1]}",
|
|
2322
|
+
"values": values,
|
|
2323
|
+
},
|
|
2324
|
+
"answer_feedback": {f"{uuids[0]}": ""},
|
|
2325
|
+
"scoring_algorithm": "VaryPointsByAnswer",
|
|
2326
|
+
"interaction_type_slug": "choice",
|
|
2327
|
+
"feedback": {},
|
|
2328
|
+
},
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
return q
|
|
2332
|
+
|
|
2333
|
+
def genNewQuizMultipleChoice(
|
|
2334
|
+
self, question, options, correct_ind, points=1, position=0
|
|
2335
|
+
):
|
|
2336
|
+
"""Generate a new quiz-style multiple choice question dictionary
|
|
2337
|
+
|
|
2338
|
+
Args:
|
|
2339
|
+
question (str):
|
|
2340
|
+
The raw question text (may include LaTeX)
|
|
2341
|
+
options (list(str) or ~numpy.ndarray(str)):
|
|
2342
|
+
Possible answers. All contents must be strings (may include LaTeX)
|
|
2343
|
+
correct_ind (ind):
|
|
2344
|
+
Index of correct answer in `options` input.
|
|
2345
|
+
points (int):
|
|
2346
|
+
Number of points for correct answer (defaults to 1).
|
|
2347
|
+
position (int):
|
|
2348
|
+
Position of question in quiz. Defaults to 0
|
|
2349
|
+
|
|
2350
|
+
Returns:
|
|
2351
|
+
dict:
|
|
2352
|
+
Question-defining dictionary
|
|
2353
|
+
|
|
2354
|
+
"""
|
|
2355
|
+
|
|
2356
|
+
# generate UUIDs
|
|
2357
|
+
uuids = [uuid.uuid4() for _ in range(len(options))]
|
|
2358
|
+
|
|
2359
|
+
# create choices dict list
|
|
2360
|
+
choices = []
|
|
2361
|
+
for j in range(len(options)):
|
|
2362
|
+
choices.append(
|
|
2363
|
+
{
|
|
2364
|
+
"id": f"{uuids[j]}",
|
|
2365
|
+
"position": j + 1,
|
|
2366
|
+
"item_body": f"<p>{convalllatex(options[j])}</p>",
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
|
|
2370
|
+
q = {
|
|
2371
|
+
"position": position,
|
|
2372
|
+
"points_possible": float(points),
|
|
2373
|
+
"properties": {},
|
|
2374
|
+
"entry_type": "Item",
|
|
2375
|
+
"entry_editable": True,
|
|
2376
|
+
"stimulus_quiz_entry_id": "",
|
|
2377
|
+
"status": "mutable",
|
|
2378
|
+
"entry": {
|
|
2379
|
+
"title": question,
|
|
2380
|
+
"item_body": f"<p>{convalllatex(question)}</p>",
|
|
2381
|
+
"calculator_type": "none",
|
|
2382
|
+
"interaction_data": {"choices": choices},
|
|
2383
|
+
"properties": {
|
|
2384
|
+
"shuffle_rules": {"choices": {"to_lock": [], "shuffled": False}},
|
|
2385
|
+
"vary_points_by_answer": False,
|
|
2386
|
+
},
|
|
2387
|
+
"scoring_data": {"value": f"{uuids[correct_ind]}"},
|
|
2388
|
+
"answer_feedback": {},
|
|
2389
|
+
"scoring_algorithm": "Equivalence",
|
|
2390
|
+
"interaction_type_slug": "choice",
|
|
2391
|
+
"feedback": {},
|
|
2392
|
+
},
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
return q
|
|
2396
|
+
|
|
2397
|
+
def genQuizMultipleChoice(
|
|
2398
|
+
self, question, options, correct_ind, points=1, position=0
|
|
2399
|
+
):
|
|
2400
|
+
""" "Generate a classic quiz-style multiple choice question dictionary
|
|
2401
|
+
|
|
2402
|
+
Args:
|
|
2403
|
+
question (str):
|
|
2404
|
+
The raw question text (may include LaTeX)
|
|
2405
|
+
options (list(str) or ~numpy.ndarray(str)):
|
|
2406
|
+
Possible answers. All contents must be strings (may include LaTeX)
|
|
2407
|
+
correct_ind (ind):
|
|
2408
|
+
Index of correct answer in `options` input.
|
|
2409
|
+
points (int):
|
|
2410
|
+
Number of points for correct answer (defaults to 1).
|
|
2411
|
+
position (int):
|
|
2412
|
+
Position of question in quiz. Defaults to 0
|
|
2413
|
+
|
|
2414
|
+
Returns:
|
|
2415
|
+
dict:
|
|
2416
|
+
Question-defining dictionary
|
|
2417
|
+
|
|
2418
|
+
"""
|
|
2419
|
+
|
|
2420
|
+
# create answers dict list
|
|
2421
|
+
answers = []
|
|
2422
|
+
for j in range(len(options)):
|
|
2423
|
+
answers.append(
|
|
2424
|
+
{
|
|
2425
|
+
"answer_html": f"<p>{convalllatex(options[j])}</p>",
|
|
2426
|
+
"answer_weight": 100 if j == correct_ind else 0,
|
|
2427
|
+
}
|
|
2428
|
+
)
|
|
2429
|
+
|
|
2430
|
+
q = {
|
|
2431
|
+
"question_name": question,
|
|
2432
|
+
"question_text": f"<p>{convalllatex(question)}</p>",
|
|
2433
|
+
"question_type": "multiple_choice_question",
|
|
2434
|
+
"position": 0,
|
|
2435
|
+
"points_possible": points,
|
|
2436
|
+
"answers": answers,
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
return q
|
|
2440
|
+
|
|
2441
|
+
def genQuizFromPolev(self, allqs, quiz):
|
|
2442
|
+
"""Generate quiz items from a PollEv formatted CSV input file
|
|
2443
|
+
|
|
2444
|
+
Args:
|
|
2445
|
+
allqs (pandas.DataFrame):
|
|
2446
|
+
Table of questions and answers, formatted in PolEV CSV upload style
|
|
2447
|
+
quiz (canvasapi.new_quiz.NewQuiz or canvasapi.quiz.Quiz):
|
|
2448
|
+
Quiz object
|
|
2449
|
+
|
|
2450
|
+
Returns:
|
|
2451
|
+
None
|
|
2452
|
+
|
|
2453
|
+
.. note::
|
|
2454
|
+
Poll Everywhere upload format documentation is available here:
|
|
2455
|
+
https://support.polleverywhere.com/hc/en-us/articles/1260801546530-Import-questions
|
|
2456
|
+
|
|
2457
|
+
"""
|
|
2458
|
+
|
|
2459
|
+
assert isinstance(
|
|
2460
|
+
quiz, (canvasapi.new_quiz.NewQuiz, canvasapi.quiz.Quiz)
|
|
2461
|
+
), "quiz input must be a Quiz or New Quiz object."
|
|
2462
|
+
|
|
2463
|
+
# identify response columns:
|
|
2464
|
+
optcols = []
|
|
2465
|
+
for col in allqs.columns:
|
|
2466
|
+
if col.startswith("Option"):
|
|
2467
|
+
optcols.append(col)
|
|
2468
|
+
|
|
2469
|
+
# iterate through rows, creating the questions
|
|
2470
|
+
for j, row in allqs.iterrows():
|
|
2471
|
+
question = row.Title
|
|
2472
|
+
opts = row[optcols].values
|
|
2473
|
+
opts = opts[~pandas.isna(opts)]
|
|
2474
|
+
correct_ind = [
|
|
2475
|
+
j
|
|
2476
|
+
for j, s in enumerate(opts)
|
|
2477
|
+
if isinstance(s, str) and s.startswith("***")
|
|
2478
|
+
]
|
|
2479
|
+
assert len(correct_ind) == 1
|
|
2480
|
+
correct_ind = correct_ind[0]
|
|
2481
|
+
opts[correct_ind] = opts[correct_ind].strip("***")
|
|
2482
|
+
|
|
2483
|
+
# create and add the question depending on quiz type
|
|
2484
|
+
if isinstance(quiz, canvasapi.new_quiz.NewQuiz):
|
|
2485
|
+
q = self.genNewQuizMultipleChoice(
|
|
2486
|
+
question, opts.astype(str), correct_ind, position=j + 1
|
|
2487
|
+
)
|
|
2488
|
+
|
|
2489
|
+
self.addNewQuizItem(quiz.id, q)
|
|
2490
|
+
else:
|
|
2491
|
+
q = self.genQuizMultipleChoice(
|
|
2492
|
+
question, opts.astype(str), correct_ind, position=j + 1
|
|
2493
|
+
)
|
|
2494
|
+
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.0}/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
|