cornellGrading 2.17.0__tar.gz → 2.18.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.17.0/cornellGrading.egg-info → cornellgrading-2.18.0}/PKG-INFO +1 -1
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/__init__.py +1 -1
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/cornellGrading.py +359 -65
- {cornellgrading-2.17.0 → cornellgrading-2.18.0/cornellGrading.egg-info}/PKG-INFO +1 -1
- cornellgrading-2.18.0/pyproject.toml +81 -0
- cornellgrading-2.17.0/pyproject.toml +0 -3
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/LICENSE +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/MANIFEST.in +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/README.md +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/cornellInterface.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/cornellQualtrics.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/dueDatesFromCSV.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/pandocHTMLParser.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading/upload_MC_questions.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading.egg-info/SOURCES.txt +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading.egg-info/dependency_links.txt +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading.egg-info/entry_points.txt +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading.egg-info/requires.txt +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/cornellGrading.egg-info/top_level.txt +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/scripts/canvastex.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/scripts/matlabgrader.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/scripts/setupSelfGrading.py +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/setup.cfg +0 -0
- {cornellgrading-2.17.0 → cornellgrading-2.18.0}/setup.py +0 -0
|
@@ -15,6 +15,7 @@ from cornellGrading.cornellQualtrics import cornellQualtrics
|
|
|
15
15
|
import urllib.parse
|
|
16
16
|
import subprocess
|
|
17
17
|
import shutil
|
|
18
|
+
import requests
|
|
18
19
|
|
|
19
20
|
try:
|
|
20
21
|
from cornellGrading.pandocHTMLParser import pandocHTMLParser
|
|
@@ -209,6 +210,7 @@ class cornellGrading:
|
|
|
209
210
|
Name of assignment to return. Must be exact match.
|
|
210
211
|
To see all assignments do:
|
|
211
212
|
>> for a in c.course.get_assignments(): print(a)
|
|
213
|
+
|
|
212
214
|
Returns:
|
|
213
215
|
canvasapi.assignment.Assignment:
|
|
214
216
|
The Assignment object
|
|
@@ -234,6 +236,7 @@ class cornellGrading:
|
|
|
234
236
|
Name of assignment group to return. Must be exact match.
|
|
235
237
|
To see all assignments do:
|
|
236
238
|
>> for a in c.course.get_assignment_groups(): print(a)
|
|
239
|
+
|
|
237
240
|
Returns:
|
|
238
241
|
canvasapi.assignment.AssignmentGroup:
|
|
239
242
|
The assignment group object
|
|
@@ -255,8 +258,9 @@ class cornellGrading:
|
|
|
255
258
|
"""Create assignment group by name
|
|
256
259
|
|
|
257
260
|
Args:
|
|
258
|
-
|
|
261
|
+
groupName (str):
|
|
259
262
|
Name of assignment group to create. Cannot be name of existing group
|
|
263
|
+
|
|
260
264
|
Returns:
|
|
261
265
|
canvasapi.assignment.AssignmentGroup:
|
|
262
266
|
The assignment group object
|
|
@@ -277,10 +281,11 @@ class cornellGrading:
|
|
|
277
281
|
"""Locate folder by name
|
|
278
282
|
|
|
279
283
|
Args:
|
|
280
|
-
(str):
|
|
284
|
+
folderName (str):
|
|
281
285
|
Name of folder to return. Must be exact match.
|
|
282
286
|
To see all assignments do:
|
|
283
287
|
>> for a in c.course.get_folders(): print(a.name)
|
|
288
|
+
|
|
284
289
|
Returns:
|
|
285
290
|
canvasapi.folder.Folder:
|
|
286
291
|
The folder object
|
|
@@ -313,6 +318,7 @@ class cornellGrading:
|
|
|
313
318
|
hidden (bool):
|
|
314
319
|
Whether to toggle to hidden (false by default). If parent folder is
|
|
315
320
|
hidden, you cannot make the subfolder visible.
|
|
321
|
+
|
|
316
322
|
Returns:
|
|
317
323
|
canvasapi.folder.Folder:
|
|
318
324
|
The folder object
|
|
@@ -478,7 +484,9 @@ class cornellGrading:
|
|
|
478
484
|
|
|
479
485
|
return pg
|
|
480
486
|
|
|
481
|
-
def createPage(
|
|
487
|
+
def createPage(
|
|
488
|
+
self, title, body, editing_roles="teachers", published=False, publish_at=None
|
|
489
|
+
):
|
|
482
490
|
"""Create a Page
|
|
483
491
|
|
|
484
492
|
Args:
|
|
@@ -489,7 +497,10 @@ class cornellGrading:
|
|
|
489
497
|
editing_roles (str):
|
|
490
498
|
See canvas API. Comma sepeated string, defaults to "teachers"
|
|
491
499
|
published (bool):
|
|
492
|
-
Whether page is published on create (defaults
|
|
500
|
+
Whether page is published on create (defaults False)
|
|
501
|
+
publish_at (datetime.datetime):
|
|
502
|
+
Publish date (not included if None). Must be timezone aware and UTC! If
|
|
503
|
+
set, overrides any inputs for published. Defaults None.
|
|
493
504
|
|
|
494
505
|
Returns:
|
|
495
506
|
canvasapi.page.Page:
|
|
@@ -503,6 +514,8 @@ class cornellGrading:
|
|
|
503
514
|
"""
|
|
504
515
|
|
|
505
516
|
assert isinstance(editing_roles, str), "editing_roles must be a string."
|
|
517
|
+
if publish_at is not None:
|
|
518
|
+
published = False
|
|
506
519
|
|
|
507
520
|
wiki_page = {
|
|
508
521
|
"title": title,
|
|
@@ -510,6 +523,8 @@ class cornellGrading:
|
|
|
510
523
|
"editing_roles": editing_roles,
|
|
511
524
|
"published": published,
|
|
512
525
|
}
|
|
526
|
+
if publish_at:
|
|
527
|
+
wiki_page["publish_at"] = publish_at.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
513
528
|
|
|
514
529
|
res = self.course.create_page(wiki_page=wiki_page)
|
|
515
530
|
|
|
@@ -523,6 +538,7 @@ class cornellGrading:
|
|
|
523
538
|
hidden=True,
|
|
524
539
|
editing_roles="teachers",
|
|
525
540
|
published=False,
|
|
541
|
+
publish_at=None,
|
|
526
542
|
insertPDF=False,
|
|
527
543
|
):
|
|
528
544
|
"""Generate a new canvas page out of a LaTex source file
|
|
@@ -546,6 +562,9 @@ class cornellGrading:
|
|
|
546
562
|
See canvas API. Comma sepeated string, defaults to "teachers"
|
|
547
563
|
published (bool):
|
|
548
564
|
Whether page is published on create (defaults False)
|
|
565
|
+
publish_at (datetime.datetime):
|
|
566
|
+
Publish date (not included if None). Must be timezone aware and UTC! If
|
|
567
|
+
set, overrides any inputs for published. Defaults None.
|
|
549
568
|
insertPDF (bool):
|
|
550
569
|
If true, also include the original file in the page (this assumes that
|
|
551
570
|
fname points at the compiled PDF and not the source).
|
|
@@ -558,6 +577,7 @@ class cornellGrading:
|
|
|
558
577
|
Requires pandoc to be installed and callable!
|
|
559
578
|
|
|
560
579
|
.. warning::
|
|
580
|
+
|
|
561
581
|
Uploaded files will overwrite files of the same name in the upload folder.
|
|
562
582
|
|
|
563
583
|
"""
|
|
@@ -585,7 +605,11 @@ class cornellGrading:
|
|
|
585
605
|
body += " " + out
|
|
586
606
|
|
|
587
607
|
res = self.createPage(
|
|
588
|
-
title,
|
|
608
|
+
title,
|
|
609
|
+
body,
|
|
610
|
+
editing_roles=editing_roles,
|
|
611
|
+
published=published,
|
|
612
|
+
publish_at=publish_at,
|
|
589
613
|
)
|
|
590
614
|
|
|
591
615
|
return res
|
|
@@ -1011,6 +1035,8 @@ class cornellGrading:
|
|
|
1011
1035
|
Due date in format: YYYY-MM-DD (5pm assumed local time)
|
|
1012
1036
|
hwfile (str):
|
|
1013
1037
|
Full path to homework file
|
|
1038
|
+
totscore (int, float):
|
|
1039
|
+
Total number of points for the assignment. Defaults to 10.
|
|
1014
1040
|
unlockDelta (float):
|
|
1015
1041
|
Unlock this number of days before due date. Defaults to None,
|
|
1016
1042
|
which makes the assignment initially unlocked.
|
|
@@ -1211,36 +1237,31 @@ class cornellGrading:
|
|
|
1211
1237
|
f.write(ll)
|
|
1212
1238
|
|
|
1213
1239
|
# run pandoc
|
|
1214
|
-
if hwd:
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1240
|
+
if not (hwd):
|
|
1241
|
+
hwd = os.path.curdir
|
|
1242
|
+
|
|
1243
|
+
pandoc_comm = [
|
|
1244
|
+
"pandoc",
|
|
1245
|
+
os.path.join(tmpdir, texf),
|
|
1246
|
+
"-s",
|
|
1247
|
+
"--webtex",
|
|
1248
|
+
"-o",
|
|
1249
|
+
htmlf,
|
|
1250
|
+
"--default-image-extension=png",
|
|
1251
|
+
]
|
|
1252
|
+
|
|
1253
|
+
if shutil.which("pandoc-crossref") is not None:
|
|
1254
|
+
pandoc_comm += ["--filter", "pandoc-crossref"]
|
|
1255
|
+
have_crossref = True
|
|
1229
1256
|
else:
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
"--default-image-extension=png",
|
|
1239
|
-
],
|
|
1240
|
-
cwd=os.path.curdir,
|
|
1241
|
-
check=True,
|
|
1242
|
-
capture_output=True,
|
|
1243
|
-
)
|
|
1257
|
+
have_crossref = False
|
|
1258
|
+
|
|
1259
|
+
_ = subprocess.run(
|
|
1260
|
+
pandoc_comm,
|
|
1261
|
+
cwd=hwd,
|
|
1262
|
+
check=True,
|
|
1263
|
+
capture_output=True,
|
|
1264
|
+
)
|
|
1244
1265
|
|
|
1245
1266
|
assert os.path.exists(htmlf), "Cannot locate html output %s" % htmlf
|
|
1246
1267
|
|
|
@@ -1250,13 +1271,7 @@ class cornellGrading:
|
|
|
1250
1271
|
|
|
1251
1272
|
upfolder = self.createFolder(folder, hidden=hidden)
|
|
1252
1273
|
|
|
1253
|
-
#
|
|
1254
|
-
repdict = {
|
|
1255
|
-
r'class="math display"': r'style="display: block; text-align: center; margin: 0.5rem auto;"',
|
|
1256
|
-
r'img style="vertical-align:middle"': r'img class="equation_image"',
|
|
1257
|
-
r'src="https://latex.codecogs.com/png.latex\?': r'src="https://canvas.cornell.edu/equation_images/',
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1274
|
+
# latex conversion
|
|
1260
1275
|
p = re.compile(r'src="https://latex.codecogs.com/png.latex\?(.*?)"')
|
|
1261
1276
|
|
|
1262
1277
|
def convlatex(x):
|
|
@@ -1269,21 +1284,10 @@ class cornellGrading:
|
|
|
1269
1284
|
parser.feed(line)
|
|
1270
1285
|
if parser.inBody:
|
|
1271
1286
|
tmp = p.sub(convlatex, line)
|
|
1272
|
-
for k, v in repdict.items():
|
|
1273
|
-
tmp = re.sub(k, v, tmp)
|
|
1274
|
-
|
|
1275
|
-
while parser.imagesUploaded:
|
|
1276
|
-
imup = parser.imagesUploaded.pop()
|
|
1277
|
-
canvasimurl = (
|
|
1278
|
-
r'src="https://canvas.cornell.edu{0}/preview" '
|
|
1279
|
-
r'data-api-endpoint="https://canvas.cornell.edu/api/'
|
|
1280
|
-
r'v1{0}" data-api-returntype="File" '.format(imup["url"])
|
|
1281
|
-
)
|
|
1282
|
-
tmp = re.sub(r'src="{0}"'.format(imup["orig"]), canvasimurl, tmp)
|
|
1283
1287
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1288
|
+
if (len(parser.figcaptions) > 0) and not (have_crossref):
|
|
1289
|
+
figcap = parser.figcaptions.pop()
|
|
1290
|
+
tmp = re.sub(r'alt=""', r'alt="{0}"'.format(figcap), tmp)
|
|
1287
1291
|
|
|
1288
1292
|
if parser.spanDefs:
|
|
1289
1293
|
for cl, val in parser.spanDefs.items():
|
|
@@ -1294,10 +1298,43 @@ class cornellGrading:
|
|
|
1294
1298
|
|
|
1295
1299
|
out.append(tmp)
|
|
1296
1300
|
|
|
1301
|
+
# put everything together into a isngle string
|
|
1297
1302
|
out = out[1:]
|
|
1298
|
-
# out = ' #strdelim# '.join(out)
|
|
1299
1303
|
out = " ".join(out)
|
|
1300
1304
|
|
|
1305
|
+
# global replacements
|
|
1306
|
+
repdict = {
|
|
1307
|
+
r'class="math display"': r'style="display: block; text-align: center; margin: 0.5rem auto;"',
|
|
1308
|
+
r'img[\s]*style="vertical-align:middle"': r'img class="equation_image"',
|
|
1309
|
+
r'src="https://latex.codecogs.com/png.latex\?': r'src="https://canvas.cornell.edu/equation_images/',
|
|
1310
|
+
r"<figcaption": '<figcaption style="text-align: center;"',
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
for k, v in repdict.items():
|
|
1314
|
+
out = re.sub(k, v, out)
|
|
1315
|
+
|
|
1316
|
+
# handle all uploaded figures:
|
|
1317
|
+
while parser.imagesUploaded:
|
|
1318
|
+
imup = parser.imagesUploaded.pop()
|
|
1319
|
+
canvasimurl = (
|
|
1320
|
+
r'src="https://canvas.cornell.edu{0}/preview" '
|
|
1321
|
+
r'data-api-endpoint="https://canvas.cornell.edu/api/'
|
|
1322
|
+
r'v1{0}" data-api-returntype="File" '.format(imup["url"])
|
|
1323
|
+
)
|
|
1324
|
+
out = re.sub(r'src="{0}"'.format(imup["orig"]), canvasimurl, out)
|
|
1325
|
+
|
|
1326
|
+
# figures less than 100% width get centered:
|
|
1327
|
+
p2 = re.compile(r'style="([\S]*)?(?=")"')
|
|
1328
|
+
|
|
1329
|
+
def convwidth(x):
|
|
1330
|
+
return re.sub(
|
|
1331
|
+
x.group(1),
|
|
1332
|
+
f"{x.group(1)}; display: block; margin-left: auto; margin-right: auto;",
|
|
1333
|
+
x.group(),
|
|
1334
|
+
)
|
|
1335
|
+
|
|
1336
|
+
out = p2.sub(convwidth, out)
|
|
1337
|
+
|
|
1301
1338
|
# handle any labeled equations
|
|
1302
1339
|
if parser.eqlabels:
|
|
1303
1340
|
for label, (eqnum, labelstr) in parser.eqlabels.items():
|
|
@@ -1306,14 +1343,10 @@ class cornellGrading:
|
|
|
1306
1343
|
# for every equation image with a label, find it, and replace with a
|
|
1307
1344
|
# span with an equation number
|
|
1308
1345
|
imtag = re.search(
|
|
1309
|
-
|
|
1310
|
-
# r'(<img class="equation_image"(.*?)(?={})([\s\S]*?)(?=/>)/>)'.format(
|
|
1311
|
-
# r'(<img class="equation_image" src="https://canvas.cornell.edu/equation_images/([\w%]*?)(?={})([\s\S]*?)(?=/>)/>)'.format(
|
|
1312
|
-
r'(<img class="equation_image" src="https://canvas.cornell.edu/equation_images/([\w%.-]*?)(?={})([\s\S]*?)(?=/>)/>)'.format(
|
|
1313
|
-
enclabel
|
|
1314
|
-
),
|
|
1346
|
+
rf'(<img class="equation_image"[\s]*src="https://canvas.cornell.edu/equation_images/([\w%.-]*?)(?={enclabel})([\s\S]*?)(?=/>)/>)',
|
|
1315
1347
|
out,
|
|
1316
1348
|
)
|
|
1349
|
+
|
|
1317
1350
|
imspan = (
|
|
1318
1351
|
r'<span style="margin: 1ex auto; display: table; '
|
|
1319
1352
|
r'text-align: center; width: 100%; vertical-align: middle;"> '
|
|
@@ -1332,11 +1365,10 @@ class cornellGrading:
|
|
|
1332
1365
|
out = out.replace("[{}]".format(label), "{}".format(eqnum))
|
|
1333
1366
|
|
|
1334
1367
|
# handle any figure labels
|
|
1335
|
-
if parser.figLabels:
|
|
1368
|
+
if (len(parser.figLabels) > 0) and not (have_crossref):
|
|
1336
1369
|
for cl, val in parser.figLabels.items():
|
|
1337
1370
|
out = out.replace(cl, val)
|
|
1338
1371
|
|
|
1339
|
-
# out = out.split(' #strdelim# ')
|
|
1340
1372
|
return out
|
|
1341
1373
|
|
|
1342
1374
|
def genPrivateHWSurvey(self, surveyname, nprobs, scoreOptions=None, ecprobs=[]):
|
|
@@ -1599,7 +1631,7 @@ class cornellGrading:
|
|
|
1599
1631
|
Number of assignment. Name of survey will be
|
|
1600
1632
|
"self.coursename HW# Self-Grade"
|
|
1601
1633
|
Name of assignment will be HW#
|
|
1602
|
-
|
|
1634
|
+
ecscore (int):
|
|
1603
1635
|
Extra credit score (defaults to 3)
|
|
1604
1636
|
checkLate (bool):
|
|
1605
1637
|
Check for late submissions (defaults true)
|
|
@@ -1962,3 +1994,265 @@ class cornellGrading:
|
|
|
1962
1994
|
item["position"] = position
|
|
1963
1995
|
|
|
1964
1996
|
module.create_module_item(item)
|
|
1997
|
+
|
|
1998
|
+
def addNewQuizItem(self, quizid, item):
|
|
1999
|
+
"""
|
|
2000
|
+
|
|
2001
|
+
Args:
|
|
2002
|
+
quizid (int):
|
|
2003
|
+
ID of New Quiz to add item to.\
|
|
2004
|
+
item (dict):
|
|
2005
|
+
Item payload dictionary. This is the contents of the 'item' key in the
|
|
2006
|
+
JSON payload. For details, see:
|
|
2007
|
+
https://canvas.instructure.com/doc/api/new_quiz_items.html#Question+Types-appendix
|
|
2008
|
+
|
|
2009
|
+
"""
|
|
2010
|
+
|
|
2011
|
+
full_url = "{}{}".format(
|
|
2012
|
+
self.course._requester.new_quizzes_url,
|
|
2013
|
+
"courses/{}/quizzes/{}/items".format(self.course.id, quizid),
|
|
2014
|
+
)
|
|
2015
|
+
headers = {
|
|
2016
|
+
"Authorization": "Bearer {}".format(self.course._requester.access_token)
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
r = requests.post(full_url, json={"item": item}, headers=headers)
|
|
2020
|
+
assert r.status_code == 200
|
|
2021
|
+
|
|
2022
|
+
def getHomework(self, assignmentNum, preamble=""):
|
|
2023
|
+
"""Retrieve assignment based on number and optional preamble
|
|
2024
|
+
|
|
2025
|
+
Args:
|
|
2026
|
+
assignmentNum (int):
|
|
2027
|
+
Assignment names will be of the form "[preamble] HW#"
|
|
2028
|
+
where # is `assignmentNum`. See also `preamble` input.
|
|
2029
|
+
|
|
2030
|
+
preamble (str):
|
|
2031
|
+
If not "", then assignment name will be "[preamble] HW#". Otherwise,
|
|
2032
|
+
name will be "HW#". Defaults to ""
|
|
2033
|
+
|
|
2034
|
+
Returns:
|
|
2035
|
+
tuple:
|
|
2036
|
+
hwname (str):
|
|
2037
|
+
Full assignment name
|
|
2038
|
+
hw (canvasapi.assignment.Assignment):
|
|
2039
|
+
Assignment object
|
|
2040
|
+
"""
|
|
2041
|
+
|
|
2042
|
+
# if preamble set, make sure it ends in a space
|
|
2043
|
+
if preamble != "":
|
|
2044
|
+
preamble = f"{preamble.strip()} "
|
|
2045
|
+
|
|
2046
|
+
# generate the name and grab the assignment
|
|
2047
|
+
hwname = f"{preamble}HW{assignmentNum}"
|
|
2048
|
+
hw = self.getAssignment(hwname)
|
|
2049
|
+
|
|
2050
|
+
return hwname, hw
|
|
2051
|
+
|
|
2052
|
+
def setupNewQuizSelfAssessment(
|
|
2053
|
+
self,
|
|
2054
|
+
assignmentNum,
|
|
2055
|
+
nprobs,
|
|
2056
|
+
solfile,
|
|
2057
|
+
item,
|
|
2058
|
+
preamble="",
|
|
2059
|
+
selfGradeDueDelta=7,
|
|
2060
|
+
selfGradeReleasedDelta=3,
|
|
2061
|
+
):
|
|
2062
|
+
"""Create a page with reference solutions and a New Quiz linking to the page for
|
|
2063
|
+
student self-assemssent.
|
|
2064
|
+
|
|
2065
|
+
Args:
|
|
2066
|
+
assignmentNum (int):
|
|
2067
|
+
Number of assignment.
|
|
2068
|
+
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
2069
|
+
nprobs (int):
|
|
2070
|
+
Number of howmework problems
|
|
2071
|
+
solfile (str):
|
|
2072
|
+
Full path to solutions file to upload
|
|
2073
|
+
item (dict):
|
|
2074
|
+
New Quiz item payload dictionary. See `addNewQuizItem`
|
|
2075
|
+
preamble (str):
|
|
2076
|
+
Preamble for all naming.
|
|
2077
|
+
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
2078
|
+
selfGradeDueDelta (float):
|
|
2079
|
+
Days after initial hw duedate that self-grading is due
|
|
2080
|
+
selfGradeReleasedDelta (float):
|
|
2081
|
+
Days after initial hw duedate that self-grading (and solutions) are
|
|
2082
|
+
released.
|
|
2083
|
+
|
|
2084
|
+
Returns:
|
|
2085
|
+
None
|
|
2086
|
+
|
|
2087
|
+
"""
|
|
2088
|
+
|
|
2089
|
+
# ensure solutions file exists
|
|
2090
|
+
assert os.path.exists(solfile)
|
|
2091
|
+
|
|
2092
|
+
# get original assignment
|
|
2093
|
+
hwname, hw = self.getHomework(assignmentNum, preamble=preamble)
|
|
2094
|
+
|
|
2095
|
+
# figure out all dates
|
|
2096
|
+
duedate = datetime.strptime(hw.due_at, """%Y-%m-%dT%H:%M:%S%z""")
|
|
2097
|
+
unlockdate = duedate + timedelta(days=selfGradeReleasedDelta)
|
|
2098
|
+
selfgradeduedate = unlockdate + timedelta(days=selfGradeDueDelta)
|
|
2099
|
+
|
|
2100
|
+
# generate reference solutions page
|
|
2101
|
+
pagename = f"{hwname} Solutions"
|
|
2102
|
+
|
|
2103
|
+
hwfoldername = "Homeworks/HW%d" % assignmentNum
|
|
2104
|
+
_ = self.createFolder(hwfoldername, hidden=True)
|
|
2105
|
+
|
|
2106
|
+
p = self.latex2page(
|
|
2107
|
+
solfile,
|
|
2108
|
+
pagename,
|
|
2109
|
+
folder=hwfoldername,
|
|
2110
|
+
publish_at=unlockdate,
|
|
2111
|
+
)
|
|
2112
|
+
|
|
2113
|
+
# get self-assessment assignment group and generate new quiz there
|
|
2114
|
+
assgrp = self.getAssignmentGroup("Homework Self-Assessment")
|
|
2115
|
+
|
|
2116
|
+
instructions = (
|
|
2117
|
+
rf"<p>{hwname} Solutions are available here: "
|
|
2118
|
+
rf'<a title={pagename} href="{p.html_url.split(".edu")[-1]}" '
|
|
2119
|
+
rf'data-course-type="wikiPages">{pagename}</a>. Check your work and score '
|
|
2120
|
+
r" yourself based on the rubric in the syllabus.</p>"
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2123
|
+
nq = self.course.create_new_quiz(
|
|
2124
|
+
quiz={
|
|
2125
|
+
"title": f"{hwname} Self-Assessment",
|
|
2126
|
+
"assignment_group_id": assgrp.id,
|
|
2127
|
+
"points_possible": hw.points_possible,
|
|
2128
|
+
"due_at": selfgradeduedate,
|
|
2129
|
+
"unlock_at": unlockdate,
|
|
2130
|
+
"grading_type": "points",
|
|
2131
|
+
"instructions": instructions,
|
|
2132
|
+
}
|
|
2133
|
+
)
|
|
2134
|
+
|
|
2135
|
+
# add new quiz items
|
|
2136
|
+
for j in range(nprobs):
|
|
2137
|
+
tmp = item.copy()
|
|
2138
|
+
tmp["position"] = j + 1
|
|
2139
|
+
tmp["entry"]["title"] = f"Problem {j+1} score"
|
|
2140
|
+
self.addNewQuizItem(nq.id, tmp)
|
|
2141
|
+
|
|
2142
|
+
# publish quiz
|
|
2143
|
+
ass = self.getAssignment(nq.title)
|
|
2144
|
+
ass.edit(assignment={"published": True})
|
|
2145
|
+
|
|
2146
|
+
def linearTimePenalty(self, hw, latePenalty=0.25, maxDaysLate=3):
|
|
2147
|
+
"""Compute late penalty as linearly increasing in time.
|
|
2148
|
+
|
|
2149
|
+
Args:
|
|
2150
|
+
hw (canvasapi.assignment.Assignment):
|
|
2151
|
+
Assignment object
|
|
2152
|
+
latePenalty (float):
|
|
2153
|
+
Maximum fraction of score to remove for lateness (defaults to 0.25).
|
|
2154
|
+
Must be in (0,1).
|
|
2155
|
+
maxDaysLate (float):
|
|
2156
|
+
After this number of days past deadline, HW gets zero. Defaults to 3.
|
|
2157
|
+
Students submitting at exactly the maximum time will recieve the maximum
|
|
2158
|
+
late penalty.
|
|
2159
|
+
|
|
2160
|
+
Returns:
|
|
2161
|
+
dict:
|
|
2162
|
+
Dictionary of late penalties in absolute points. Keys are netids.
|
|
2163
|
+
The dictionary will only contain values for students with submissions.
|
|
2164
|
+
|
|
2165
|
+
"""
|
|
2166
|
+
|
|
2167
|
+
totscore = hw.points_possible
|
|
2168
|
+
duedate = datetime.strptime(hw.due_at, """%Y-%m-%dT%H:%M:%S%z""")
|
|
2169
|
+
|
|
2170
|
+
lates = {}
|
|
2171
|
+
for t in hw.get_submissions():
|
|
2172
|
+
if t.user_id in self.ids:
|
|
2173
|
+
netid = self.netids[self.ids == t.user_id][0]
|
|
2174
|
+
|
|
2175
|
+
if t.submitted_at:
|
|
2176
|
+
subtime = datetime.strptime(
|
|
2177
|
+
t.submitted_at, """%Y-%m-%dT%H:%M:%S%z"""
|
|
2178
|
+
)
|
|
2179
|
+
if not t.late:
|
|
2180
|
+
tdelta = 0
|
|
2181
|
+
else:
|
|
2182
|
+
tdelta = np.abs((duedate - subtime).total_seconds()) / 86400
|
|
2183
|
+
|
|
2184
|
+
if tdelta > maxDaysLate:
|
|
2185
|
+
lates[netid] = totscore
|
|
2186
|
+
else:
|
|
2187
|
+
lates[netid] = tdelta / maxDaysLate * totscore * latePenalty
|
|
2188
|
+
|
|
2189
|
+
return lates
|
|
2190
|
+
|
|
2191
|
+
def importNewQuizSelfAssessment(
|
|
2192
|
+
self,
|
|
2193
|
+
assignmentNum,
|
|
2194
|
+
latePenalty=0.25,
|
|
2195
|
+
maxDaysLate=3,
|
|
2196
|
+
preamble="",
|
|
2197
|
+
):
|
|
2198
|
+
"""
|
|
2199
|
+
|
|
2200
|
+
Args:
|
|
2201
|
+
assignmentNum (int):
|
|
2202
|
+
Number of assignment.
|
|
2203
|
+
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
2204
|
+
latePenalty (float):
|
|
2205
|
+
Maximum fraction of score to remove for lateness (defaults to 0.25).
|
|
2206
|
+
Must be in (0,1).
|
|
2207
|
+
maxDaysLate (float):
|
|
2208
|
+
After this number of days past deadline, HW gets zero. Defaults to 3.
|
|
2209
|
+
Students submitting at exactly the maximum time will recieve the maximum
|
|
2210
|
+
late penalty.
|
|
2211
|
+
preamble (str):
|
|
2212
|
+
Preamble for all naming.
|
|
2213
|
+
See :py:meth:`cornellGrading.cornellGrading.getHomework` for details.
|
|
2214
|
+
|
|
2215
|
+
Returns:
|
|
2216
|
+
tuple:
|
|
2217
|
+
submittedScoreNoAssignment (list):
|
|
2218
|
+
netids of students who submitted scores but not the original
|
|
2219
|
+
assignment
|
|
2220
|
+
submittedAssignmentNoScore (list):
|
|
2221
|
+
netids of students who submitted original assignments but not
|
|
2222
|
+
self-assessment scores
|
|
2223
|
+
|
|
2224
|
+
"""
|
|
2225
|
+
|
|
2226
|
+
# get original assignments
|
|
2227
|
+
hwname, hw = self.getHomework(assignmentNum, preamble=preamble)
|
|
2228
|
+
|
|
2229
|
+
# compute late penalties
|
|
2230
|
+
lates = self.linearTimePenalty(
|
|
2231
|
+
hw, latePenalty=latePenalty, maxDaysLate=maxDaysLate
|
|
2232
|
+
)
|
|
2233
|
+
|
|
2234
|
+
# get the self-grading assignment and process submissions
|
|
2235
|
+
sg = self.getAssignment(f"{hwname} Self-Assessment")
|
|
2236
|
+
netids = []
|
|
2237
|
+
scores = []
|
|
2238
|
+
submittedScoreNoAssignment = []
|
|
2239
|
+
for sub in sg.get_submissions():
|
|
2240
|
+
if sub.user_id not in self.ids:
|
|
2241
|
+
continue
|
|
2242
|
+
if sub.grade is None:
|
|
2243
|
+
continue
|
|
2244
|
+
netid = self.netids[self.ids == sub.user_id][0]
|
|
2245
|
+
if netid not in lates:
|
|
2246
|
+
print(f"{netid} submitted self-grading but not the assignment!")
|
|
2247
|
+
submittedScoreNoAssignment.append(netid)
|
|
2248
|
+
continue
|
|
2249
|
+
|
|
2250
|
+
netids.append(netid)
|
|
2251
|
+
scores.append(float(sub.grade) - lates[netid])
|
|
2252
|
+
|
|
2253
|
+
# identify students with submitted assignments but no self-assessments
|
|
2254
|
+
submittedAssignmentNoScore = list(set(lates.keys()) - set(netids))
|
|
2255
|
+
|
|
2256
|
+
self.uploadScores(hw, netids, scores)
|
|
2257
|
+
|
|
2258
|
+
return submittedScoreNoAssignment, submittedAssignmentNoScore
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.ruff]
|
|
6
|
+
# Exclude a variety of commonly ignored directories.
|
|
7
|
+
exclude = [
|
|
8
|
+
".bzr",
|
|
9
|
+
".direnv",
|
|
10
|
+
".eggs",
|
|
11
|
+
".git",
|
|
12
|
+
".git-rewrite",
|
|
13
|
+
".hg",
|
|
14
|
+
".ipynb_checkpoints",
|
|
15
|
+
".mypy_cache",
|
|
16
|
+
".nox",
|
|
17
|
+
".pants.d",
|
|
18
|
+
".pyenv",
|
|
19
|
+
".pytest_cache",
|
|
20
|
+
".pytype",
|
|
21
|
+
".ruff_cache",
|
|
22
|
+
".svn",
|
|
23
|
+
".tox",
|
|
24
|
+
".venv",
|
|
25
|
+
".vscode",
|
|
26
|
+
"__pypackages__",
|
|
27
|
+
"_build",
|
|
28
|
+
"buck-out",
|
|
29
|
+
"build",
|
|
30
|
+
"dist",
|
|
31
|
+
"node_modules",
|
|
32
|
+
"site-packages",
|
|
33
|
+
"venv",
|
|
34
|
+
"tests",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
# Same as Black.
|
|
38
|
+
line-length = 88
|
|
39
|
+
indent-width = 4
|
|
40
|
+
|
|
41
|
+
# Assume Python 3.10
|
|
42
|
+
target-version = "py310"
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E4", "E7", "E9", "F", "D101", "D102", "D103", "D105", "D106", "D207", "D208", "D416", "D417","D419"]
|
|
46
|
+
ignore = ["E741", "E731", "E203"]
|
|
47
|
+
|
|
48
|
+
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
49
|
+
fixable = ["ALL"]
|
|
50
|
+
unfixable = []
|
|
51
|
+
|
|
52
|
+
# Allow unused variables when underscore-prefixed.
|
|
53
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
54
|
+
|
|
55
|
+
[tool.ruff.format]
|
|
56
|
+
# Like Black, use double quotes for strings.
|
|
57
|
+
quote-style = "double"
|
|
58
|
+
|
|
59
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
60
|
+
indent-style = "space"
|
|
61
|
+
|
|
62
|
+
# Like Black, respect magic trailing commas.
|
|
63
|
+
skip-magic-trailing-comma = false
|
|
64
|
+
|
|
65
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
66
|
+
line-ending = "auto"
|
|
67
|
+
|
|
68
|
+
# Enable auto-formatting of code examples in docstrings. Markdown,
|
|
69
|
+
# reStructuredText code/literal blocks and doctests are all supported.
|
|
70
|
+
#
|
|
71
|
+
# This is currently disabled by default, but it is planned for this
|
|
72
|
+
# to be opt-out in the future.
|
|
73
|
+
docstring-code-format = false
|
|
74
|
+
|
|
75
|
+
# Set the line length limit used when formatting code snippets in
|
|
76
|
+
# docstrings.
|
|
77
|
+
#
|
|
78
|
+
# This only has an effect when the `docstring-code-format` setting is
|
|
79
|
+
# enabled.
|
|
80
|
+
docstring-code-line-length = "dynamic"
|
|
81
|
+
|
|
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
|
{cornellgrading-2.17.0 → cornellgrading-2.18.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
|