PyKubeGrader 0.1.11__tar.gz → 0.1.13__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pykubegrader-0.1.11/src/PyKubeGrader.egg-info → pykubegrader-0.1.13}/PKG-INFO +1 -1
- {pykubegrader-0.1.11 → pykubegrader-0.1.13/src/PyKubeGrader.egg-info}/PKG-INFO +1 -1
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/build/api_notebook_builder.py +18 -16
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/build/build_folder.py +36 -6
- pykubegrader-0.1.13/src/pykubegrader/initialize.py +106 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/telemetry.py +6 -7
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/validate.py +2 -2
- pykubegrader-0.1.11/src/pykubegrader/initialize.py +0 -68
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/.coveragerc +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/.github/workflows/main.yml +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/.gitignore +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/.readthedocs.yml +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/AUTHORS.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/CHANGELOG.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/CONTRIBUTING.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/LICENSE.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/README.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/Makefile +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/_static/custom.css +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/authors.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/changelog.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/conf.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/contributing.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/index.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/license.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/readme.rst +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/docs/requirements.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/examples/.responses.json +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/examples/true_false.ipynb +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/pyproject.toml +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/setup.cfg +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/setup.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/requires.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/__init__.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/utils.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/__init__.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/multiple_choice.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/reading_question.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/select_many.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/student_info.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/style.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/true_false.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets/types_question.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets_base/__init__.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets_base/multi_select.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets_base/reading.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/src/pykubegrader/widgets_base/select.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/tests/conftest.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/tests/import_test.py +0 -0
- {pykubegrader-0.1.11 → pykubegrader-0.1.13}/tox.ini +0 -0
@@ -1,11 +1,12 @@
|
|
1
|
+
import ast
|
2
|
+
import json
|
3
|
+
import re
|
4
|
+
import shutil
|
1
5
|
from dataclasses import dataclass
|
2
6
|
from pathlib import Path
|
3
7
|
from typing import Optional
|
4
|
-
|
8
|
+
|
5
9
|
import nbformat
|
6
|
-
import json
|
7
|
-
import re
|
8
|
-
import shutil
|
9
10
|
|
10
11
|
|
11
12
|
@dataclass
|
@@ -20,7 +21,6 @@ class FastAPINotebookBuilder:
|
|
20
21
|
self.run()
|
21
22
|
|
22
23
|
def run(self):
|
23
|
-
|
24
24
|
# here for easy debugging
|
25
25
|
if self.temp_notebook is not None:
|
26
26
|
shutil.copy(
|
@@ -34,7 +34,6 @@ class FastAPINotebookBuilder:
|
|
34
34
|
self.add_api_code()
|
35
35
|
|
36
36
|
def add_api_code(self):
|
37
|
-
|
38
37
|
for i, (cell_index, cell_dict) in enumerate(self.assertion_tests_dict.items()):
|
39
38
|
print(
|
40
39
|
f"Processing cell {cell_index + 1}, {i} of {len(self.assertion_tests_dict)}"
|
@@ -77,7 +76,9 @@ class FastAPINotebookBuilder:
|
|
77
76
|
)
|
78
77
|
updated_cell_source.extend(["earned_points += score\n"])
|
79
78
|
updated_cell_source.extend(
|
80
|
-
[
|
79
|
+
[
|
80
|
+
f'log_variable(f"{{score}}, {{max_score}}", question_id, "{self.filename.split(".")[0]}")\n'
|
81
|
+
]
|
81
82
|
)
|
82
83
|
updated_cell_source.extend(
|
83
84
|
["os.environ['EARNED_POINTS'] = str(earned_points)\n"]
|
@@ -105,7 +106,6 @@ class FastAPINotebookBuilder:
|
|
105
106
|
@staticmethod
|
106
107
|
def construct_update_responses(cell_dict):
|
107
108
|
update_responses = []
|
108
|
-
question_id = cell_dict["question"] + "-" + str(cell_dict["test_number"]) + "\n"
|
109
109
|
|
110
110
|
logging_variables = cell_dict["logging_variables"]
|
111
111
|
|
@@ -141,7 +141,6 @@ class FastAPINotebookBuilder:
|
|
141
141
|
|
142
142
|
@staticmethod
|
143
143
|
def construct_graders(cell_dict):
|
144
|
-
|
145
144
|
# Generate Python code
|
146
145
|
added_code = [
|
147
146
|
"if "
|
@@ -205,7 +204,7 @@ class FastAPINotebookBuilder:
|
|
205
204
|
" ensure_responses,\n",
|
206
205
|
" log_variable,\n",
|
207
206
|
" score_question,\n",
|
208
|
-
"
|
207
|
+
" submit_question,\n",
|
209
208
|
" telemetry,\n",
|
210
209
|
" update_responses,\n",
|
211
210
|
")\n",
|
@@ -283,7 +282,7 @@ class FastAPINotebookBuilder:
|
|
283
282
|
"""
|
284
283
|
last_import_index = -1
|
285
284
|
is_multiline_import = False # Flag to track if we're inside a multiline import
|
286
|
-
|
285
|
+
|
287
286
|
for i, line in enumerate(cell_source):
|
288
287
|
stripped_line = line.strip()
|
289
288
|
|
@@ -313,11 +312,15 @@ class FastAPINotebookBuilder:
|
|
313
312
|
if "source" in cell:
|
314
313
|
for line in cell["source"]:
|
315
314
|
# Look for the log_variables pattern
|
316
|
-
match = re.search(r"log_variables:\s
|
315
|
+
match = re.search(r"log_variables:\s*(\[.*\])", line)
|
317
316
|
if match:
|
318
|
-
#
|
319
|
-
|
320
|
-
|
317
|
+
# Parse the list using ast.literal_eval for safety
|
318
|
+
try:
|
319
|
+
log_variables = ast.literal_eval(match.group(1))
|
320
|
+
if isinstance(log_variables, list):
|
321
|
+
return [var.strip() for var in log_variables]
|
322
|
+
except (SyntaxError, ValueError):
|
323
|
+
pass
|
321
324
|
return []
|
322
325
|
|
323
326
|
def tag_questions(cells_dict):
|
@@ -363,7 +366,6 @@ class FastAPINotebookBuilder:
|
|
363
366
|
return cells_dict
|
364
367
|
|
365
368
|
def question_dict(self):
|
366
|
-
|
367
369
|
notebook_path = Path(self.temp_notebook)
|
368
370
|
if not notebook_path.exists():
|
369
371
|
raise FileNotFoundError(f"The file {notebook_path} does not exist.")
|
@@ -8,7 +8,9 @@ import shutil
|
|
8
8
|
import subprocess
|
9
9
|
import sys
|
10
10
|
from dataclasses import dataclass, field
|
11
|
+
|
11
12
|
import nbformat
|
13
|
+
|
12
14
|
from .api_notebook_builder import FastAPINotebookBuilder
|
13
15
|
|
14
16
|
|
@@ -278,26 +280,38 @@ class NotebookProcessor:
|
|
278
280
|
self, temp_notebook_path, notebook_subfolder, notebook_name
|
279
281
|
):
|
280
282
|
if self.has_assignment(temp_notebook_path, "# ASSIGNMENT CONFIG"):
|
281
|
-
|
282
283
|
# TODO: This is hardcoded for now, but should be in a configuration file.
|
283
284
|
client_private_key = os.path.join(
|
284
285
|
notebook_subfolder,
|
285
|
-
"client_private_key.bin",
|
286
|
+
".client_private_key.bin",
|
286
287
|
)
|
287
288
|
server_public_key = os.path.join(
|
288
289
|
notebook_subfolder,
|
289
|
-
"server_public_key.bin",
|
290
|
+
".server_public_key.bin",
|
290
291
|
)
|
291
292
|
|
292
|
-
shutil.copy("./keys
|
293
|
-
shutil.copy("./keys
|
293
|
+
shutil.copy("./keys/.client_private_key.bin", client_private_key)
|
294
|
+
shutil.copy("./keys/.server_public_key.bin", server_public_key)
|
294
295
|
|
295
296
|
FastAPINotebookBuilder(notebook_path=temp_notebook_path)
|
296
297
|
|
298
|
+
debug_notebook = os.path.join(
|
299
|
+
notebook_subfolder,
|
300
|
+
"dist",
|
301
|
+
"autograder",
|
302
|
+
os.path.basename(temp_notebook_path).replace("_temp", "_debugger"),
|
303
|
+
)
|
304
|
+
|
297
305
|
self.run_otter_assign(
|
298
306
|
temp_notebook_path, os.path.join(notebook_subfolder, "dist")
|
299
307
|
)
|
300
308
|
|
309
|
+
print(f"Copying {temp_notebook_path} to {debug_notebook}")
|
310
|
+
|
311
|
+
shutil.copy(temp_notebook_path, debug_notebook)
|
312
|
+
|
313
|
+
NotebookProcessor.remove_assignment_config_cells(debug_notebook)
|
314
|
+
|
301
315
|
student_notebook = os.path.join(
|
302
316
|
notebook_subfolder, "dist", "student", f"{notebook_name}.ipynb"
|
303
317
|
)
|
@@ -329,6 +343,23 @@ class NotebookProcessor:
|
|
329
343
|
NotebookProcessor.add_initialization_code(temp_notebook_path)
|
330
344
|
return None
|
331
345
|
|
346
|
+
@staticmethod
|
347
|
+
def remove_assignment_config_cells(notebook_path):
|
348
|
+
# Read the notebook
|
349
|
+
with open(notebook_path, "r", encoding="utf-8") as f:
|
350
|
+
notebook = nbformat.read(f, as_version=nbformat.NO_CONVERT)
|
351
|
+
|
352
|
+
# Filter out cells containing "# ASSIGNMENT CONFIG"
|
353
|
+
notebook.cells = [
|
354
|
+
cell
|
355
|
+
for cell in notebook.cells
|
356
|
+
if "# ASSIGNMENT CONFIG" not in cell.get("source", "")
|
357
|
+
]
|
358
|
+
|
359
|
+
# Save the updated notebook
|
360
|
+
with open(notebook_path, "w", encoding="utf-8") as f:
|
361
|
+
nbformat.write(notebook, f)
|
362
|
+
|
332
363
|
@staticmethod
|
333
364
|
def add_initialization_code(notebook_path):
|
334
365
|
# finds the first code cell
|
@@ -340,7 +371,6 @@ class NotebookProcessor:
|
|
340
371
|
replace_cell_source(notebook_path, index, cell)
|
341
372
|
|
342
373
|
def multiple_choice_parser(self, temp_notebook_path, new_notebook_path):
|
343
|
-
|
344
374
|
### Parse the notebook for multiple choice questions
|
345
375
|
if self.has_assignment(temp_notebook_path, "# BEGIN MULTIPLE CHOICE"):
|
346
376
|
self._print_and_log(
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
import panel as pn
|
6
|
+
import requests
|
7
|
+
from IPython import get_ipython
|
8
|
+
|
9
|
+
from .telemetry import ensure_responses, telemetry, update_responses
|
10
|
+
|
11
|
+
|
12
|
+
def initialize_assignment(
|
13
|
+
name: str,
|
14
|
+
url: str = "https://engr-131-api.eastus.cloudapp.azure.com/",
|
15
|
+
verbose: bool = False,
|
16
|
+
) -> dict:
|
17
|
+
"""
|
18
|
+
Initialize an assignment in a Jupyter environment.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
name (str): The name of the assignment.
|
22
|
+
url (str): The URL of the API server.
|
23
|
+
verbose (bool): Whether to print detailed initialization information.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
dict: The responses dictionary after initialization.
|
27
|
+
|
28
|
+
Raises:
|
29
|
+
Exception: If the environment is unsupported or initialization fails.
|
30
|
+
"""
|
31
|
+
|
32
|
+
ipython = get_ipython()
|
33
|
+
if ipython is None:
|
34
|
+
raise Exception("Setup unsuccessful. Are you in a Jupyter environment?")
|
35
|
+
|
36
|
+
try:
|
37
|
+
move_dotfiles()
|
38
|
+
ipython.events.register("pre_run_cell", telemetry)
|
39
|
+
except Exception as e:
|
40
|
+
raise Exception(f"Failed to register telemetry: {e}")
|
41
|
+
|
42
|
+
jhub_user = os.getenv("JUPYTERHUB_USER")
|
43
|
+
if jhub_user is None:
|
44
|
+
raise Exception("Setup unsuccessful. Are you on JupyterHub?")
|
45
|
+
|
46
|
+
try:
|
47
|
+
seed = hash(jhub_user) % 1000
|
48
|
+
update_responses(key="seed", value=seed)
|
49
|
+
|
50
|
+
update_responses(key="assignment", value=name)
|
51
|
+
update_responses(key="jhub_user", value=jhub_user)
|
52
|
+
|
53
|
+
responses = ensure_responses()
|
54
|
+
# TODO: Add more checks here?
|
55
|
+
assert isinstance(responses.get("seed"), int), "Seed not set"
|
56
|
+
|
57
|
+
pn.extension(silent=True)
|
58
|
+
|
59
|
+
# Check connection to API server
|
60
|
+
params = {"jhub_user": responses["jhub_user"]}
|
61
|
+
response = requests.get(url, params=params)
|
62
|
+
if verbose:
|
63
|
+
print(f"status code: {response.status_code}")
|
64
|
+
data = response.json()
|
65
|
+
for k, v in data.items():
|
66
|
+
print(f"{k}: {v}")
|
67
|
+
except Exception as e:
|
68
|
+
raise Exception(f"Failed to initialize assignment: {e}")
|
69
|
+
|
70
|
+
print("Assignment successfully initialized")
|
71
|
+
if verbose:
|
72
|
+
print(f"Assignment: {name}")
|
73
|
+
print(f"Username: {jhub_user}")
|
74
|
+
|
75
|
+
return responses
|
76
|
+
|
77
|
+
|
78
|
+
#
|
79
|
+
# Helper functions
|
80
|
+
#
|
81
|
+
|
82
|
+
|
83
|
+
def move_dotfiles():
|
84
|
+
"""
|
85
|
+
Move essential dotfiles from a fixed source directory to the current working directory.
|
86
|
+
|
87
|
+
Raises:
|
88
|
+
FileNotFoundError: If a source file is missing.
|
89
|
+
Exception: If copying fails for any other reason.
|
90
|
+
"""
|
91
|
+
source_dir = Path("/opt/dotfiles")
|
92
|
+
target_dir = Path.cwd()
|
93
|
+
|
94
|
+
files_to_copy = [".client_private_key.bin", ".server_public_key.bin"]
|
95
|
+
|
96
|
+
for file_name in files_to_copy:
|
97
|
+
source_file = source_dir / file_name
|
98
|
+
target_file = target_dir / file_name
|
99
|
+
|
100
|
+
if not source_file.exists():
|
101
|
+
raise FileNotFoundError(f"Key file not found: {source_file}")
|
102
|
+
|
103
|
+
try:
|
104
|
+
shutil.copy2(source_file, target_file)
|
105
|
+
except Exception as e:
|
106
|
+
raise Exception(f"Failed to copy {source_file} to {target_file}: {e}")
|
@@ -20,11 +20,11 @@ logging.basicConfig(filename=".output.log", level=logging.INFO, force=True)
|
|
20
20
|
|
21
21
|
|
22
22
|
def encrypt_to_b64(message: str) -> str:
|
23
|
-
with open("server_public_key.bin", "rb") as f:
|
23
|
+
with open(".server_public_key.bin", "rb") as f:
|
24
24
|
server_pub_key_bytes = f.read()
|
25
25
|
server_pub_key = nacl.public.PublicKey(server_pub_key_bytes)
|
26
26
|
|
27
|
-
with open("client_private_key.bin", "rb") as f:
|
27
|
+
with open(".client_private_key.bin", "rb") as f:
|
28
28
|
client_private_key_bytes = f.read()
|
29
29
|
client_priv_key = nacl.public.PrivateKey(client_private_key_bytes)
|
30
30
|
|
@@ -36,7 +36,6 @@ def encrypt_to_b64(message: str) -> str:
|
|
36
36
|
|
37
37
|
|
38
38
|
def ensure_responses() -> dict:
|
39
|
-
|
40
39
|
with open(".responses.json", "a") as _:
|
41
40
|
pass
|
42
41
|
|
@@ -48,7 +47,7 @@ def ensure_responses() -> dict:
|
|
48
47
|
except json.JSONDecodeError:
|
49
48
|
with open(".responses.json", "w") as f:
|
50
49
|
json.dump(responses, f)
|
51
|
-
|
50
|
+
|
52
51
|
return responses
|
53
52
|
|
54
53
|
|
@@ -57,9 +56,9 @@ def log_encrypted(message: str) -> None:
|
|
57
56
|
logging.info(f"Encrypted Output: {encrypted_b64}")
|
58
57
|
|
59
58
|
|
60
|
-
def log_variable(value, info_type) -> None:
|
59
|
+
def log_variable(assignment_name, value, info_type) -> None:
|
61
60
|
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
62
|
-
message = f"{info_type}, {value}, {timestamp}"
|
61
|
+
message = f"{info_type}, {value}, {timestamp}, {assignment_name}"
|
63
62
|
log_encrypted(message)
|
64
63
|
|
65
64
|
|
@@ -120,7 +119,7 @@ def score_question(
|
|
120
119
|
return res
|
121
120
|
|
122
121
|
|
123
|
-
def
|
122
|
+
def submit_question(
|
124
123
|
student_email: str,
|
125
124
|
term: str,
|
126
125
|
assignment: str,
|
@@ -252,11 +252,11 @@ def validate_logfile(
|
|
252
252
|
|
253
253
|
|
254
254
|
def generate_keys() -> nacl.public.Box:
|
255
|
-
with open("server_private_key.bin", "rb") as priv_file:
|
255
|
+
with open(".server_private_key.bin", "rb") as priv_file:
|
256
256
|
server_private_key_bytes = priv_file.read()
|
257
257
|
server_priv_key = nacl.public.PrivateKey(server_private_key_bytes)
|
258
258
|
|
259
|
-
with open("client_public_key.bin", "rb") as pub_file:
|
259
|
+
with open(".client_public_key.bin", "rb") as pub_file:
|
260
260
|
client_public_key_bytes = pub_file.read()
|
261
261
|
client_pub_key = nacl.public.PublicKey(client_public_key_bytes)
|
262
262
|
|
@@ -1,68 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import os
|
3
|
-
from typing import Optional
|
4
|
-
|
5
|
-
import panel as pn
|
6
|
-
from IPython import get_ipython
|
7
|
-
import requests
|
8
|
-
from .telemetry import telemetry, update_responses, ensure_responses
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def initialize_assignment(name: str,
|
13
|
-
verbose: Optional[bool] = False,
|
14
|
-
url: Optional[str] = "https://engr-131-api.eastus.cloudapp.azure.com/") -> None:
|
15
|
-
|
16
|
-
ipython = get_ipython()
|
17
|
-
if ipython is None:
|
18
|
-
print("Setup unsuccessful. Are you in a Jupyter environment?")
|
19
|
-
return
|
20
|
-
|
21
|
-
try:
|
22
|
-
ipython.events.register("pre_run_cell", telemetry)
|
23
|
-
except TypeError as e:
|
24
|
-
print(f"Failed to register telemetry: {e}")
|
25
|
-
return
|
26
|
-
|
27
|
-
jhub_user = os.getenv("JUPYTERHUB_USER")
|
28
|
-
if jhub_user is None:
|
29
|
-
print("Setup unsuccessful. Are you on JupyterHub?")
|
30
|
-
return
|
31
|
-
|
32
|
-
try:
|
33
|
-
seed = hash(jhub_user) % 1000
|
34
|
-
update_responses(key="seed", value=seed)
|
35
|
-
update_responses(key="assignment", value=name)
|
36
|
-
update_responses(key="jhub_user", value=jhub_user)
|
37
|
-
|
38
|
-
except (TypeError, json.JSONDecodeError) as e:
|
39
|
-
print(f"Failed to initialize assignment: {e}")
|
40
|
-
return
|
41
|
-
|
42
|
-
|
43
|
-
# extract responses
|
44
|
-
responses = ensure_responses()
|
45
|
-
|
46
|
-
# TODO: Add more checks here??
|
47
|
-
assert isinstance(responses.get('seed'), int), "valid seed not found in responses"
|
48
|
-
|
49
|
-
pn.extension(silent=True)
|
50
|
-
|
51
|
-
if verbose:
|
52
|
-
print("Assignment successfully initialized")
|
53
|
-
print(f"Assignment: {name}")
|
54
|
-
print(f"Username: {jhub_user}")
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# Checks connectivity to the API
|
59
|
-
params = { "jhub_user": responses["jhub_user"] }
|
60
|
-
response = requests.get(url, params=params)
|
61
|
-
if verbose:
|
62
|
-
print(f"status code: {response.status_code}")
|
63
|
-
data = response.json()
|
64
|
-
for k, v in data.items():
|
65
|
-
print(f"{k}: {v}")
|
66
|
-
|
67
|
-
print("Assignment successfully initialized")
|
68
|
-
return responses
|
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
|
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
|
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
|
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
|
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
|