cornellGrading 2.25.0__tar.gz → 2.27.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.25.0/cornellGrading.egg-info → cornellgrading-2.27.0}/PKG-INFO +1 -1
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/__init__.py +2 -1
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/cornellGrading.py +3 -7
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/cornellQualtrics.py +79 -55
- cornellgrading-2.27.0/cornellGrading/simplelists.py +242 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0/cornellGrading.egg-info}/PKG-INFO +1 -1
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading.egg-info/SOURCES.txt +1 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/LICENSE +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/MANIFEST.in +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/README.md +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/cornellInterface.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/dueDatesFromCSV.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/pandocHTMLParser.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/quizQuestionBank.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/uploadPollEvScores.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/upload_MC_questions.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading/utils.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading.egg-info/dependency_links.txt +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading.egg-info/entry_points.txt +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading.egg-info/requires.txt +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/cornellGrading.egg-info/top_level.txt +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/pyproject.toml +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/scripts/canvastex.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/scripts/matlabgrader.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/scripts/setupSelfGrading.py +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/setup.cfg +0 -0
- {cornellgrading-2.25.0 → cornellgrading-2.27.0}/setup.py +0 -0
|
@@ -803,7 +803,7 @@ class cornellGrading:
|
|
|
803
803
|
# check that this one doesn't already exist
|
|
804
804
|
res = self.qualtrics.getMailingLists()
|
|
805
805
|
assert self.coursename not in [
|
|
806
|
-
el["name"] for el in res
|
|
806
|
+
el["name"] for el in res
|
|
807
807
|
], "Mailing list already exists for this course."
|
|
808
808
|
|
|
809
809
|
names = np.array([n.split(", ") for n in self.names])
|
|
@@ -1011,9 +1011,7 @@ class cornellGrading:
|
|
|
1011
1011
|
<p><a class="survey-link ng-binding" href="{0}" target="_blank">{0}
|
|
1012
1012
|
</a></p>
|
|
1013
1013
|
<p>Be sure to enter your correct netid or you will not receive
|
|
1014
|
-
credit.</p>""".format(
|
|
1015
|
-
link
|
|
1016
|
-
)
|
|
1014
|
+
credit.</p>""".format(link)
|
|
1017
1015
|
|
|
1018
1016
|
_ = self.createAssignment(
|
|
1019
1017
|
assname,
|
|
@@ -1611,9 +1609,7 @@ class cornellGrading:
|
|
|
1611
1609
|
"""data-api-endpoint="{2}" data-api-returntype="File">{0}</a></p>
|
|
1612
1610
|
<p>Grade yourself against the rubric in the syllabus and enter your
|
|
1613
1611
|
scores for each problem by following the link in the comments on
|
|
1614
|
-
your original submission.</p>""".format(
|
|
1615
|
-
solfname, solurl, solepoint
|
|
1616
|
-
)
|
|
1612
|
+
your original submission.</p>""".format(solfname, solurl, solepoint)
|
|
1617
1613
|
)
|
|
1618
1614
|
|
|
1619
1615
|
if injectText:
|
|
@@ -80,6 +80,10 @@ class cornellQualtrics:
|
|
|
80
80
|
self.listSurveys()
|
|
81
81
|
print("Connected to Qualtrics.")
|
|
82
82
|
|
|
83
|
+
# set directoryId and userId
|
|
84
|
+
self.directoryId = self.getDirectoryId()
|
|
85
|
+
self.whoami()
|
|
86
|
+
|
|
83
87
|
def setupHeaders(self):
|
|
84
88
|
"""Generate standard headers
|
|
85
89
|
|
|
@@ -105,6 +109,37 @@ class cornellQualtrics:
|
|
|
105
109
|
"x-api-token": self.apiToken,
|
|
106
110
|
}
|
|
107
111
|
|
|
112
|
+
def whoami(self):
|
|
113
|
+
"""Populate userId attribute"""
|
|
114
|
+
|
|
115
|
+
baseUrl = f"https://{self.dataCenter}{self.qualtricsapi}whoami"
|
|
116
|
+
response = requests.get(baseUrl, headers=self.headers_tokenOnly)
|
|
117
|
+
|
|
118
|
+
self.userId = response.json()["result"]["userId"]
|
|
119
|
+
|
|
120
|
+
def expandResponse(self, response):
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
response (requests.models.Response):
|
|
125
|
+
Requests response from any relevant endpoint accessed with GET request
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
list:
|
|
129
|
+
list of all result elements
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
out = response.json()["result"]["elements"]
|
|
134
|
+
|
|
135
|
+
while response.json()["result"]["nextPage"] is not None:
|
|
136
|
+
response = requests.get(
|
|
137
|
+
response.json()["result"]["nextPage"], headers=self.headers_tokenOnly
|
|
138
|
+
)
|
|
139
|
+
out += response.json()["result"]["elements"]
|
|
140
|
+
|
|
141
|
+
return out
|
|
142
|
+
|
|
108
143
|
def listSurveys(self, baseUrl=None):
|
|
109
144
|
"""Grab and store all available Qualtrics surveys
|
|
110
145
|
|
|
@@ -214,25 +249,34 @@ class cornellQualtrics:
|
|
|
214
249
|
|
|
215
250
|
return response
|
|
216
251
|
|
|
217
|
-
def getMailingLists(self):
|
|
252
|
+
def getMailingLists(self, includeShared=False):
|
|
218
253
|
"""Grab all available Qualtrics mailing lists
|
|
219
254
|
|
|
220
255
|
Args:
|
|
221
|
-
|
|
256
|
+
includeShared (bool):
|
|
257
|
+
If True, include all mailing lists accessible to user. If False
|
|
258
|
+
(defualt) include only those owned by user.
|
|
222
259
|
|
|
223
260
|
Returns:
|
|
224
|
-
|
|
225
|
-
|
|
261
|
+
list:
|
|
262
|
+
all mailing list definitions
|
|
226
263
|
|
|
227
264
|
"""
|
|
228
265
|
|
|
229
|
-
baseUrl =
|
|
230
|
-
self.dataCenter
|
|
231
|
-
self.
|
|
266
|
+
baseUrl = (
|
|
267
|
+
f"https://{self.dataCenter}{self.qualtricsapi}directories/"
|
|
268
|
+
f"{self.directoryId}/mailinglists"
|
|
232
269
|
)
|
|
233
|
-
|
|
270
|
+
if includeShared:
|
|
271
|
+
response = requests.get(baseUrl, headers=self.headers_tokenOnly)
|
|
272
|
+
else:
|
|
273
|
+
response = requests.get(
|
|
274
|
+
baseUrl, headers=self.headers_tokenOnly, params={"ownerId": self.userId}
|
|
275
|
+
)
|
|
234
276
|
|
|
235
|
-
|
|
277
|
+
out = self.expandResponse(response)
|
|
278
|
+
|
|
279
|
+
return out
|
|
236
280
|
|
|
237
281
|
def getMailingListId(self, listName):
|
|
238
282
|
"""Find qualtrics mailinglist id by name. Matching is exact.
|
|
@@ -249,9 +293,9 @@ class cornellQualtrics:
|
|
|
249
293
|
|
|
250
294
|
res = self.getMailingLists()
|
|
251
295
|
mailinglistid = None
|
|
252
|
-
for el in res
|
|
296
|
+
for el in res:
|
|
253
297
|
if el["name"] == listName:
|
|
254
|
-
mailinglistid = el["
|
|
298
|
+
mailinglistid = el["mailingListId"]
|
|
255
299
|
break
|
|
256
300
|
assert mailinglistid, "Couldn't find this mailing list."
|
|
257
301
|
|
|
@@ -263,8 +307,8 @@ class cornellQualtrics:
|
|
|
263
307
|
Args:
|
|
264
308
|
listName (str):
|
|
265
309
|
List name
|
|
266
|
-
libraryId (str,
|
|
267
|
-
|
|
310
|
+
libraryId (str, deprecated):
|
|
311
|
+
DEPRECATED and ignored. Left only for backwards compatibility.
|
|
268
312
|
|
|
269
313
|
Returns:
|
|
270
314
|
str:
|
|
@@ -272,23 +316,13 @@ class cornellQualtrics:
|
|
|
272
316
|
|
|
273
317
|
"""
|
|
274
318
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
"https://{
|
|
278
|
-
|
|
319
|
+
data = {"name": listName}
|
|
320
|
+
baseUrl = (
|
|
321
|
+
f"https://{self.dataCenter}{self.qualtricsapi}directories/"
|
|
322
|
+
f"{self.directoryId}/mailinglists"
|
|
279
323
|
)
|
|
280
|
-
|
|
281
|
-
if libraryId is None:
|
|
282
|
-
for el in tmp.json()["result"]["elements"]:
|
|
283
|
-
if "UR_" in el["libraryId"] or "URH_" in el["libraryId"]:
|
|
284
|
-
libraryId = el["libraryId"]
|
|
285
|
-
break
|
|
286
|
-
assert libraryId is not None, "Could not identify library id."
|
|
287
|
-
|
|
288
|
-
data = {"libraryId": libraryId, "name": listName}
|
|
289
|
-
|
|
290
324
|
response = requests.post(
|
|
291
|
-
|
|
325
|
+
baseUrl,
|
|
292
326
|
headers=self.headers_post,
|
|
293
327
|
json=data,
|
|
294
328
|
)
|
|
@@ -304,8 +338,8 @@ class cornellQualtrics:
|
|
|
304
338
|
Mailing list name
|
|
305
339
|
contacts (pandas.DataFrame):
|
|
306
340
|
DataFrame containing the columns: 'First Name', 'Last Name', and 'Email'
|
|
307
|
-
libraryId (str,
|
|
308
|
-
|
|
341
|
+
libraryId (str, dprecated):
|
|
342
|
+
DEPRECATED and ignored. Left only for backwards compatibility.
|
|
309
343
|
|
|
310
344
|
|
|
311
345
|
Returns:
|
|
@@ -367,26 +401,18 @@ class cornellQualtrics:
|
|
|
367
401
|
|
|
368
402
|
|
|
369
403
|
"""
|
|
370
|
-
|
|
371
|
-
"https://{
|
|
372
|
-
|
|
373
|
-
mailingListId,
|
|
374
|
-
self.qualtricsapi,
|
|
375
|
-
),
|
|
376
|
-
headers=self.headers_tokenOnly,
|
|
404
|
+
baseUrl = (
|
|
405
|
+
f"https://{self.dataCenter}{self.qualtricsapi}directories/"
|
|
406
|
+
f"{self.directoryId}/mailinglists/{mailingListId}/contacts"
|
|
377
407
|
)
|
|
378
408
|
|
|
409
|
+
response = requests.get(baseUrl, headers=self.headers_tokenOnly)
|
|
410
|
+
|
|
379
411
|
assert (
|
|
380
412
|
response.status_code == 200
|
|
381
413
|
), "Could not get contacts for list {}.".format(mailingListId)
|
|
382
414
|
|
|
383
|
-
out =
|
|
384
|
-
|
|
385
|
-
while response.json()["result"]["nextPage"] is not None:
|
|
386
|
-
response = requests.get(
|
|
387
|
-
response.json()["result"]["nextPage"], headers=self.headers_tokenOnly
|
|
388
|
-
)
|
|
389
|
-
out += response.json()["result"]["elements"]
|
|
415
|
+
out = self.expandResponse(response)
|
|
390
416
|
|
|
391
417
|
return out
|
|
392
418
|
|
|
@@ -417,7 +443,7 @@ class cornellQualtrics:
|
|
|
417
443
|
for el in contacts:
|
|
418
444
|
fn.append(el["firstName"])
|
|
419
445
|
ln.append(el["lastName"])
|
|
420
|
-
cid.append(el["
|
|
446
|
+
cid.append(el["contactLookupId"])
|
|
421
447
|
email.append(el["email"])
|
|
422
448
|
|
|
423
449
|
contacts = pandas.DataFrame(
|
|
@@ -453,8 +479,9 @@ class cornellQualtrics:
|
|
|
453
479
|
|
|
454
480
|
"""
|
|
455
481
|
|
|
456
|
-
baseUrl =
|
|
457
|
-
self.dataCenter
|
|
482
|
+
baseUrl = (
|
|
483
|
+
f"https://{self.dataCenter}{self.qualtricsapi}directories/"
|
|
484
|
+
f"{self.directoryId}/mailinglists/{mailingListId}/contacts"
|
|
458
485
|
)
|
|
459
486
|
|
|
460
487
|
data = {
|
|
@@ -484,15 +511,12 @@ class cornellQualtrics:
|
|
|
484
511
|
|
|
485
512
|
"""
|
|
486
513
|
|
|
487
|
-
|
|
488
|
-
"https://{
|
|
489
|
-
|
|
490
|
-
mailingListId,
|
|
491
|
-
contactId,
|
|
492
|
-
self.qualtricsapi,
|
|
493
|
-
),
|
|
494
|
-
headers=self.headers_post,
|
|
514
|
+
baseUrl = (
|
|
515
|
+
f"https://{self.dataCenter}{self.qualtricsapi}directories/"
|
|
516
|
+
f"{self.directoryId}/mailinglists/{mailingListId}/contacts/{contactId}"
|
|
495
517
|
)
|
|
518
|
+
|
|
519
|
+
response = requests.delete(baseUrl, headers=self.headers_post)
|
|
496
520
|
assert response.status_code == 200, "Could not remove contact from list."
|
|
497
521
|
|
|
498
522
|
def genDistribution(self, surveyId, mailingListId):
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Wrapper class for interacting with the SimpleLists v2 REST API.
|
|
2
|
+
|
|
3
|
+
Provides authentication against per-list API keys stored in a local JSON
|
|
4
|
+
credentials file, plus basic contact operations (retrieve, create, delete)
|
|
5
|
+
against SimpleLists mailing lists"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class simplelists:
|
|
16
|
+
"""Class for io methods for the SimpleLists v2 API."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, server: str = "https://lists.cornell.edu/api/2/") -> None:
|
|
19
|
+
"""Load per-list API keys and validate them against the live API.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
server (str):
|
|
23
|
+
Base URL of the SimpleLists API, including trailing slash
|
|
24
|
+
and version path. Defaults to
|
|
25
|
+
'https://lists.cornell.edu/api/2/'.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
self.baseurl = server
|
|
29
|
+
|
|
30
|
+
cdir = "simplelists"
|
|
31
|
+
|
|
32
|
+
if os.name == "nt":
|
|
33
|
+
config_dir = Path(os.environ["APPDATA"], cdir)
|
|
34
|
+
else:
|
|
35
|
+
config_dir = Path(Path.home(), ".config", cdir)
|
|
36
|
+
|
|
37
|
+
credfile = config_dir / "credentials.json"
|
|
38
|
+
|
|
39
|
+
assert credfile.exists(), f"Cannot locate credentials file at {credfile}."
|
|
40
|
+
assert os.access(credfile, os.R_OK), (
|
|
41
|
+
f"Credentials file at {credfile} exists but is not readable."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.keys: Dict[str, str] = json.loads(credfile.read_text())
|
|
45
|
+
|
|
46
|
+
for account, apikey in self.keys.items():
|
|
47
|
+
server_response = requests.get(
|
|
48
|
+
f"{self.baseurl}contacts/", params={"limit": 1}, auth=(apikey, "")
|
|
49
|
+
)
|
|
50
|
+
assert server_response.status_code == 200, (
|
|
51
|
+
f"Could not authenticate SimpleLists API key for '{account}'."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def _get_api_key(self, list_name: str) -> str:
|
|
55
|
+
"""Look up the API key associated with a mailing list name.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
list_name (str):
|
|
59
|
+
Name of the list (e.g. 'MAEFIELDFACULTY-L'), matching the
|
|
60
|
+
list-name portion of a '<list_name>-account' key in the
|
|
61
|
+
loaded credentials file.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str:
|
|
65
|
+
API key for the requested list.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
account = f"{list_name}-account"
|
|
69
|
+
assert account in self.keys, (
|
|
70
|
+
f"No API key found for list '{list_name}' "
|
|
71
|
+
f"(expected key '{account}' in credentials.json)."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return self.keys[account]
|
|
75
|
+
|
|
76
|
+
def get_contacts(self, list_name: str, limit: int = 1000) -> List[Dict[str, Any]]:
|
|
77
|
+
"""Retrieve all contacts belonging to a mailing list.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
list_name (str):
|
|
81
|
+
Name of the list to query (e.g. 'MAEFIELDFACULTY-L').
|
|
82
|
+
limit (int):
|
|
83
|
+
Maximum number of records to request per page (SimpleLists
|
|
84
|
+
API allows up to 1000). Defaults to 1000.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
list:
|
|
88
|
+
Raw contact objects (as returned by the API, one dict per
|
|
89
|
+
contact) for every contact on the list.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
apikey = self._get_api_key(list_name)
|
|
93
|
+
url = f"{self.baseurl}contacts/"
|
|
94
|
+
|
|
95
|
+
records: List[Dict[str, Any]] = []
|
|
96
|
+
page = 1
|
|
97
|
+
while True:
|
|
98
|
+
server_response = requests.get(
|
|
99
|
+
url,
|
|
100
|
+
params={"list": list_name, "page": page, "limit": limit},
|
|
101
|
+
auth=(apikey, ""),
|
|
102
|
+
)
|
|
103
|
+
assert server_response.status_code == 200, (
|
|
104
|
+
f"Could not retrieve contacts for list '{list_name}' (page {page})."
|
|
105
|
+
)
|
|
106
|
+
payload = server_response.json()
|
|
107
|
+
records.extend(payload["data"])
|
|
108
|
+
|
|
109
|
+
if not payload["data"] or page * limit >= payload["count"]:
|
|
110
|
+
break
|
|
111
|
+
page += 1
|
|
112
|
+
|
|
113
|
+
return records
|
|
114
|
+
|
|
115
|
+
def create_contact(
|
|
116
|
+
self,
|
|
117
|
+
list_name: str,
|
|
118
|
+
email: str,
|
|
119
|
+
firstname: Optional[str] = None,
|
|
120
|
+
surname: Optional[str] = None,
|
|
121
|
+
notes: Optional[str] = None,
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
"""Create a new contact and add them to a mailing list.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
list_name (str):
|
|
127
|
+
Name of the list to add the new contact to.
|
|
128
|
+
email (str):
|
|
129
|
+
Primary email address for the new contact.
|
|
130
|
+
firstname (str, optional):
|
|
131
|
+
Contact's first name. Defaults to None (omitted).
|
|
132
|
+
surname (str, optional):
|
|
133
|
+
Contact's surname. Defaults to None (omitted).
|
|
134
|
+
notes (str, optional):
|
|
135
|
+
Free-text notes for the contact. Defaults to None (omitted).
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
dict:
|
|
139
|
+
Parsed JSON response describing the created contact.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
apikey = self._get_api_key(list_name)
|
|
143
|
+
url = f"{self.baseurl}contacts/"
|
|
144
|
+
|
|
145
|
+
payload: Dict[str, Any] = {
|
|
146
|
+
"emails": email,
|
|
147
|
+
"lists": list_name,
|
|
148
|
+
}
|
|
149
|
+
if firstname is not None:
|
|
150
|
+
payload["firstname"] = firstname
|
|
151
|
+
if surname is not None:
|
|
152
|
+
payload["surname"] = surname
|
|
153
|
+
if notes is not None:
|
|
154
|
+
payload["notes"] = notes
|
|
155
|
+
|
|
156
|
+
server_response = requests.post(url, data=payload, auth=(apikey, ""))
|
|
157
|
+
assert server_response.status_code == 200, (
|
|
158
|
+
f"Could not create contact '{email}' on list '{list_name}'."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return server_response.json()
|
|
162
|
+
|
|
163
|
+
def update_contact(
|
|
164
|
+
self,
|
|
165
|
+
list_name: str,
|
|
166
|
+
contact_id: int,
|
|
167
|
+
firstname: Optional[str] = None,
|
|
168
|
+
surname: Optional[str] = None,
|
|
169
|
+
notes: Optional[str] = None,
|
|
170
|
+
email: Optional[str] = None,
|
|
171
|
+
) -> Dict[str, Any]:
|
|
172
|
+
"""Update an existing contact.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
list_name (str):
|
|
176
|
+
Name of the list the API key belongs to (used only to
|
|
177
|
+
resolve the API key; SimpleLists contact ids are global,
|
|
178
|
+
not scoped to a single list).
|
|
179
|
+
contact_id (int):
|
|
180
|
+
SimpleLists internal id of the contact to update.
|
|
181
|
+
firstname (str, optional):
|
|
182
|
+
New first name. Defaults to None (left unchanged).
|
|
183
|
+
surname (str, optional):
|
|
184
|
+
New surname. Defaults to None (left unchanged).
|
|
185
|
+
notes (str, optional):
|
|
186
|
+
New free-text notes. Defaults to None (left unchanged).
|
|
187
|
+
email (str, optional):
|
|
188
|
+
New list of email addresses. Defaults to None (left
|
|
189
|
+
unchanged).
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict:
|
|
193
|
+
Parsed JSON response describing the updated contact.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
apikey = self._get_api_key(list_name)
|
|
197
|
+
url = f"{self.baseurl}contacts/{contact_id}/"
|
|
198
|
+
|
|
199
|
+
payload: Dict[str, Any] = {}
|
|
200
|
+
if firstname is not None:
|
|
201
|
+
payload["firstname"] = firstname
|
|
202
|
+
if surname is not None:
|
|
203
|
+
payload["surname"] = surname
|
|
204
|
+
if notes is not None:
|
|
205
|
+
payload["notes"] = notes
|
|
206
|
+
if email is not None:
|
|
207
|
+
payload["emails"] = email
|
|
208
|
+
|
|
209
|
+
assert payload, "At least one contact property must be provided to update."
|
|
210
|
+
|
|
211
|
+
server_response = requests.put(url, data=payload, auth=(apikey, ""))
|
|
212
|
+
assert server_response.status_code == 200, (
|
|
213
|
+
f"Could not update contact id {contact_id} (list '{list_name}')."
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return server_response.json()
|
|
217
|
+
|
|
218
|
+
def delete_contact(self, list_name: str, contact_id: int) -> bool:
|
|
219
|
+
"""Delete a contact from SimpleLists.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
list_name (str):
|
|
223
|
+
Name of the list the API key belongs to (used only to
|
|
224
|
+
resolve the API key; SimpleLists contact ids are global,
|
|
225
|
+
not scoped to a single list).
|
|
226
|
+
contact_id (int):
|
|
227
|
+
SimpleLists internal id of the contact to delete.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
bool:
|
|
231
|
+
True if the API reported successful deletion.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
apikey = self._get_api_key(list_name)
|
|
235
|
+
url = f"{self.baseurl}contacts/{contact_id}/"
|
|
236
|
+
|
|
237
|
+
server_response = requests.delete(url, auth=(apikey, ""))
|
|
238
|
+
assert server_response.status_code == 200, (
|
|
239
|
+
f"Could not delete contact id {contact_id} (list '{list_name}')."
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return server_response.json().get("success", False)
|
|
@@ -11,6 +11,7 @@ cornellGrading/cornellQualtrics.py
|
|
|
11
11
|
cornellGrading/dueDatesFromCSV.py
|
|
12
12
|
cornellGrading/pandocHTMLParser.py
|
|
13
13
|
cornellGrading/quizQuestionBank.py
|
|
14
|
+
cornellGrading/simplelists.py
|
|
14
15
|
cornellGrading/uploadPollEvScores.py
|
|
15
16
|
cornellGrading/upload_MC_questions.py
|
|
16
17
|
cornellGrading/utils.py
|
|
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
|
{cornellgrading-2.25.0 → cornellgrading-2.27.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
|