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.
Files changed (24) hide show
  1. {cornellgrading-2.19.0/cornellGrading.egg-info → cornellgrading-2.20.1}/PKG-INFO +1 -1
  2. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/__init__.py +1 -1
  3. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellGrading.py +268 -5
  4. cornellgrading-2.20.1/cornellGrading/utils.py +55 -0
  5. {cornellgrading-2.19.0 → cornellgrading-2.20.1/cornellGrading.egg-info}/PKG-INFO +1 -1
  6. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/SOURCES.txt +1 -0
  7. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/LICENSE +0 -0
  8. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/MANIFEST.in +0 -0
  9. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/README.md +0 -0
  10. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellInterface.py +0 -0
  11. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/cornellQualtrics.py +0 -0
  12. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/dueDatesFromCSV.py +0 -0
  13. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/pandocHTMLParser.py +0 -0
  14. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading/upload_MC_questions.py +0 -0
  15. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/dependency_links.txt +0 -0
  16. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/entry_points.txt +0 -0
  17. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/requires.txt +0 -0
  18. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/cornellGrading.egg-info/top_level.txt +0 -0
  19. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/pyproject.toml +0 -0
  20. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/canvastex.py +0 -0
  21. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/matlabgrader.py +0 -0
  22. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/scripts/setupSelfGrading.py +0 -0
  23. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/setup.cfg +0 -0
  24. {cornellgrading-2.19.0 → cornellgrading-2.20.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cornellGrading
3
- Version: 2.19.0
3
+ Version: 2.20.1
4
4
  Summary: Routines for interacting with Cornell installations of Canvas and Qualtrics
5
5
  Home-page: https://github.com/dsavransky/grading
6
6
  Author: Dmitry Savransky
@@ -1,4 +1,4 @@
1
1
  name = "cornellGrading"
2
- __version__ = "2.19.0"
2
+ __version__ = "2.20.1"
3
3
  from .cornellGrading import cornellGrading
4
4
  from .cornellQualtrics import cornellQualtrics
@@ -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 getHomework(self, assignmentNum, preamble=""):
2023
- """Retrieve assignment based on number and optional preamble
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
- item,
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
- item (dict):
2074
- New Quiz item payload dictionary. See `addNewQuizItem`
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cornellGrading
3
- Version: 2.19.0
3
+ Version: 2.20.1
4
4
  Summary: Routines for interacting with Cornell installations of Canvas and Qualtrics
5
5
  Home-page: https://github.com/dsavransky/grading
6
6
  Author: Dmitry Savransky
@@ -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