PyKubeGrader 0.2.25__tar.gz → 0.2.27__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pykubegrader-0.2.25/src/PyKubeGrader.egg-info → pykubegrader-0.2.27}/PKG-INFO +2 -2
- {pykubegrader-0.2.25 → pykubegrader-0.2.27/src/PyKubeGrader.egg-info}/PKG-INFO +2 -2
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/build/api_notebook_builder.py +6 -5
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/build/build_folder.py +9 -18
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/initialize.py +5 -3
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/parse.ipynb +4 -23
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/parse.py +11 -2
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/submit/submit_assignment.py +5 -4
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/telemetry.py +2 -2
- pykubegrader-0.2.27/src/pykubegrader/tokens/tokens.py +42 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/tokens/validate_token.py +22 -18
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/reading.py +4 -4
- pykubegrader-0.2.25/src/pykubegrader/tokens/tokens.py +0 -47
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/.coveragerc +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/.github/workflows/main.yml +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/.gitignore +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/.readthedocs.yml +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/AUTHORS.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/CHANGELOG.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/CONTRIBUTING.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/LICENSE.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/README.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/Makefile +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/_static/custom.css +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/authors.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/changelog.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/conf.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/contributing.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/index.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/license.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/readme.rst +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/docs/requirements.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/examples/.responses.json +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/examples/true_false.ipynb +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/pyproject.toml +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/setup.cfg +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/setup.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/requires.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/build/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/build/clean_folder.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/graders/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/graders/late_assignments.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/utils.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/validate.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/multiple_choice.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/reading_question.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/select_many.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/student_info.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/style.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/true_false.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets/types_question.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/__init__.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/multi_select.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/select.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/tests/conftest.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/tests/import_test.py +0 -0
- {pykubegrader-0.2.25 → pykubegrader-0.2.27}/tox.ini +0 -0
@@ -65,9 +65,6 @@ class FastAPINotebookBuilder:
|
|
65
65
|
updated_cell_source.extend(
|
66
66
|
FastAPINotebookBuilder.construct_question_info(cell_dict)
|
67
67
|
)
|
68
|
-
updated_cell_source.extend(
|
69
|
-
FastAPINotebookBuilder.construct_update_responses(cell_dict)
|
70
|
-
)
|
71
68
|
|
72
69
|
updated_cell_source.extend(cell_source[last_import_line_ind + 1 :])
|
73
70
|
updated_cell_source.extend(["\n"])
|
@@ -91,6 +88,10 @@ class FastAPINotebookBuilder:
|
|
91
88
|
["os.environ['EARNED_POINTS'] = str(earned_points)\n"]
|
92
89
|
)
|
93
90
|
|
91
|
+
updated_cell_source.extend(
|
92
|
+
FastAPINotebookBuilder.construct_update_responses(cell_dict)
|
93
|
+
)
|
94
|
+
|
94
95
|
self.replace_cell_source(cell_index, updated_cell_source)
|
95
96
|
|
96
97
|
def compute_max_points_free_response(self):
|
@@ -142,7 +143,7 @@ class FastAPINotebookBuilder:
|
|
142
143
|
|
143
144
|
for logging_variable in logging_variables:
|
144
145
|
update_responses.append(
|
145
|
-
f"responses = update_responses(question_id, {logging_variable})\n"
|
146
|
+
f"responses = update_responses(question_id, str({logging_variable}))\n"
|
146
147
|
)
|
147
148
|
|
148
149
|
return update_responses
|
@@ -189,7 +190,7 @@ class FastAPINotebookBuilder:
|
|
189
190
|
question_id = cell_dict["question"] + "-" + str(cell_dict["test_number"])
|
190
191
|
|
191
192
|
question_info.append(f'question_id = "{question_id}"' + "\n")
|
192
|
-
question_info.append(f
|
193
|
+
question_info.append(f"max_score = {cell_dict['points']}\n")
|
193
194
|
question_info.append("score = 0\n")
|
194
195
|
|
195
196
|
return question_info
|
@@ -22,13 +22,11 @@ try:
|
|
22
22
|
except: # noqa: E722
|
23
23
|
print("Passwords not found, cannot access database")
|
24
24
|
|
25
|
-
import nbformat
|
26
|
-
|
27
|
-
from .api_notebook_builder import FastAPINotebookBuilder
|
28
25
|
from typing import Optional
|
29
26
|
|
27
|
+
import nbformat
|
30
28
|
|
31
|
-
import
|
29
|
+
from .api_notebook_builder import FastAPINotebookBuilder
|
32
30
|
|
33
31
|
os.environ["JUPYTERHUB_USER"] = "jca92"
|
34
32
|
os.environ["TOKEN"] = "token"
|
@@ -180,9 +178,7 @@ class NotebookProcessor:
|
|
180
178
|
self.update_initialize_function()
|
181
179
|
|
182
180
|
def update_initialize_function(self):
|
183
|
-
|
184
181
|
for key, value in self.total_point_log.items():
|
185
|
-
|
186
182
|
assignment_tag = f"week{self.week_num}-{self.assignment_type}"
|
187
183
|
|
188
184
|
update_initialize_assignment(
|
@@ -724,6 +720,7 @@ class NotebookProcessor:
|
|
724
720
|
with open(notebook_path, "w", encoding="utf-8") as f:
|
725
721
|
nbformat.write(notebook, f)
|
726
722
|
|
723
|
+
@staticmethod
|
727
724
|
def add_validate_token_cell(notebook_path: str, require_key: bool) -> None:
|
728
725
|
"""
|
729
726
|
Adds a new code cell at the top of a Jupyter notebook if require_key is True.
|
@@ -794,7 +791,7 @@ class NotebookProcessor:
|
|
794
791
|
data, output_file=solution_path
|
795
792
|
)
|
796
793
|
|
797
|
-
question_path = f"{new_notebook_path.replace(
|
794
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
798
795
|
|
799
796
|
generate_mcq_file(data, output_file=question_path)
|
800
797
|
|
@@ -834,7 +831,7 @@ class NotebookProcessor:
|
|
834
831
|
data, output_file=solution_path
|
835
832
|
)
|
836
833
|
|
837
|
-
question_path = f"{new_notebook_path.replace(
|
834
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
838
835
|
|
839
836
|
generate_tf_file(data, output_file=question_path)
|
840
837
|
|
@@ -872,7 +869,7 @@ class NotebookProcessor:
|
|
872
869
|
data, output_file=solution_path
|
873
870
|
)
|
874
871
|
|
875
|
-
question_path = f"{new_notebook_path.replace(
|
872
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
876
873
|
|
877
874
|
generate_select_many_file(data, output_file=question_path)
|
878
875
|
|
@@ -1812,9 +1809,7 @@ def generate_mcq_file(data_dict, output_file="mc_questions.py"):
|
|
1812
1809
|
)
|
1813
1810
|
f.write(" def __init__(self):\n")
|
1814
1811
|
f.write(" super().__init__(\n")
|
1815
|
-
f.write(
|
1816
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1817
|
-
)
|
1812
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1818
1813
|
f.write(" style=MCQ,\n")
|
1819
1814
|
f.write(
|
1820
1815
|
f" question_number={q_value['question number']},\n"
|
@@ -1885,9 +1880,7 @@ def generate_select_many_file(data_dict, output_file="select_many_questions.py")
|
|
1885
1880
|
)
|
1886
1881
|
f.write(" def __init__(self):\n")
|
1887
1882
|
f.write(" super().__init__(\n")
|
1888
|
-
f.write(
|
1889
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1890
|
-
)
|
1883
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1891
1884
|
f.write(" style=MultiSelect,\n")
|
1892
1885
|
f.write(
|
1893
1886
|
f" question_number={q_value['question number']},\n"
|
@@ -1964,9 +1957,7 @@ def generate_tf_file(data_dict, output_file="tf_questions.py"):
|
|
1964
1957
|
)
|
1965
1958
|
f.write(" def __init__(self):\n")
|
1966
1959
|
f.write(" super().__init__(\n")
|
1967
|
-
f.write(
|
1968
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1969
|
-
)
|
1960
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1970
1961
|
f.write(" style=TFStyle,\n")
|
1971
1962
|
f.write(
|
1972
1963
|
f" question_number={q_value['question number']},\n"
|
@@ -2,14 +2,16 @@ import hashlib
|
|
2
2
|
import os
|
3
3
|
import shutil
|
4
4
|
from pathlib import Path
|
5
|
+
from typing import Optional
|
5
6
|
|
6
7
|
import panel as pn
|
7
8
|
import requests
|
8
9
|
from IPython import get_ipython
|
9
|
-
|
10
|
+
|
10
11
|
from .telemetry import ensure_responses, log_variable, telemetry, update_responses
|
11
12
|
|
12
|
-
|
13
|
+
|
14
|
+
# TODO: could cleanup to remove redundant imports
|
13
15
|
def initialize_assignment(
|
14
16
|
name: str,
|
15
17
|
week: str,
|
@@ -33,7 +35,7 @@ def initialize_assignment(
|
|
33
35
|
Raises:
|
34
36
|
Exception: If the environment is unsupported or initialization fails.
|
35
37
|
"""
|
36
|
-
|
38
|
+
|
37
39
|
if assignment_tag is None:
|
38
40
|
assignment_tag = f"{week}-{assignment_type}"
|
39
41
|
|
@@ -6,17 +6,12 @@
|
|
6
6
|
"metadata": {},
|
7
7
|
"outputs": [],
|
8
8
|
"source": [
|
9
|
+
"# ruff: noqa\n",
|
10
|
+
"\n",
|
9
11
|
"# Get the public/private keypair for decryption\n",
|
10
12
|
"key_box = get_keybox()"
|
11
13
|
]
|
12
14
|
},
|
13
|
-
{
|
14
|
-
"cell_type": "code",
|
15
|
-
"execution_count": null,
|
16
|
-
"metadata": {},
|
17
|
-
"outputs": [],
|
18
|
-
"source": []
|
19
|
-
},
|
20
15
|
{
|
21
16
|
"cell_type": "code",
|
22
17
|
"execution_count": null,
|
@@ -151,7 +146,7 @@
|
|
151
146
|
"parser = LogParser(log_lines=log_lines, week_tag=\"week1-readings\")\n",
|
152
147
|
"parser.parse_logs()\n",
|
153
148
|
"parser.calculate_total_scores()\n",
|
154
|
-
"results = parser.get_results()
|
149
|
+
"results = parser.get_results()\n",
|
155
150
|
"\n",
|
156
151
|
"results"
|
157
152
|
]
|
@@ -192,13 +187,6 @@
|
|
192
187
|
"results"
|
193
188
|
]
|
194
189
|
},
|
195
|
-
{
|
196
|
-
"cell_type": "code",
|
197
|
-
"execution_count": null,
|
198
|
-
"metadata": {},
|
199
|
-
"outputs": [],
|
200
|
-
"source": []
|
201
|
-
},
|
202
190
|
{
|
203
191
|
"cell_type": "code",
|
204
192
|
"execution_count": null,
|
@@ -218,7 +206,7 @@
|
|
218
206
|
"time_stamp = results[\"student_information\"][\"timestamp\"]\n",
|
219
207
|
"\n",
|
220
208
|
"week_num = results[\"week_num\"]\n",
|
221
|
-
"assignment_type = results[
|
209
|
+
"assignment_type = results[\"assignment_type\"]\n",
|
222
210
|
"\n",
|
223
211
|
"assignments_graded = results[\"assignment_information\"].keys()\n",
|
224
212
|
"total_score = 0\n",
|
@@ -253,13 +241,6 @@
|
|
253
241
|
"source": [
|
254
242
|
"student_email"
|
255
243
|
]
|
256
|
-
},
|
257
|
-
{
|
258
|
-
"cell_type": "code",
|
259
|
-
"execution_count": null,
|
260
|
-
"metadata": {},
|
261
|
-
"outputs": [],
|
262
|
-
"source": []
|
263
244
|
}
|
264
245
|
],
|
265
246
|
"metadata": {
|
@@ -1,5 +1,14 @@
|
|
1
1
|
from dataclasses import dataclass, field
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Any, Optional, TypedDict
|
3
|
+
|
4
|
+
|
5
|
+
class LogParserResults(TypedDict):
|
6
|
+
student_information: dict[str, str]
|
7
|
+
week: Optional[str]
|
8
|
+
week_num: Optional[int]
|
9
|
+
assignment_type: Optional[str]
|
10
|
+
assignment_information: dict[str, Any]
|
11
|
+
assignment_scores: dict[str, Any]
|
3
12
|
|
4
13
|
|
5
14
|
@dataclass
|
@@ -160,7 +169,7 @@ class LogParser:
|
|
160
169
|
total_score = sum(q["score_earned"] for q in data["questions"].values())
|
161
170
|
data["total_score"] = total_score
|
162
171
|
|
163
|
-
def get_results(self) ->
|
172
|
+
def get_results(self) -> LogParserResults:
|
164
173
|
"""
|
165
174
|
Returns the parsed results as a hierarchical dictionary with three sections:
|
166
175
|
"""
|
@@ -1,8 +1,9 @@
|
|
1
|
-
import os
|
2
|
-
import httpx
|
3
1
|
import asyncio
|
4
|
-
import nest_asyncio
|
5
2
|
import base64
|
3
|
+
import os
|
4
|
+
|
5
|
+
import httpx
|
6
|
+
import nest_asyncio # type: ignore
|
6
7
|
|
7
8
|
# Apply nest_asyncio for environments like Jupyter
|
8
9
|
nest_asyncio.apply()
|
@@ -43,7 +44,7 @@ async def call_score_assignment(
|
|
43
44
|
# Get credentials
|
44
45
|
credentials = get_credentials()
|
45
46
|
username = credentials["username"]
|
46
|
-
password = credentials["password"]
|
47
|
+
password = credentials["password"]
|
47
48
|
|
48
49
|
# Encode credentials for Basic Authentication
|
49
50
|
auth_header = (
|
@@ -134,11 +134,11 @@ def score_question(
|
|
134
134
|
responses = ensure_responses()
|
135
135
|
|
136
136
|
payload: dict[str, Any] = {
|
137
|
-
"student_email": f
|
137
|
+
"student_email": f"{responses['jhub_user']}@drexel.edu",
|
138
138
|
"term": term,
|
139
139
|
"week": responses["week"],
|
140
140
|
"assignment": responses["assignment_type"],
|
141
|
-
"question": f
|
141
|
+
"question": f"_{responses['assignment']}",
|
142
142
|
"responses": responses,
|
143
143
|
}
|
144
144
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from requests.auth import HTTPBasicAuth
|
5
|
+
|
6
|
+
|
7
|
+
def build_token_payload(token: str, duration: int) -> dict:
|
8
|
+
jhub_user = os.getenv("JUPYTERHUB_USER")
|
9
|
+
if jhub_user is None:
|
10
|
+
raise ValueError("JupyterHub user not found")
|
11
|
+
|
12
|
+
return {
|
13
|
+
"value": token,
|
14
|
+
"duration": duration,
|
15
|
+
"requester": jhub_user,
|
16
|
+
}
|
17
|
+
|
18
|
+
|
19
|
+
def add_token(token: str, duration: int = 20) -> None:
|
20
|
+
"""
|
21
|
+
Sends a POST request to mint a token
|
22
|
+
"""
|
23
|
+
|
24
|
+
url = "https://engr-131-api.eastus.cloudapp.azure.com/tokens"
|
25
|
+
|
26
|
+
payload = build_token_payload(token=token, duration=duration)
|
27
|
+
|
28
|
+
# Dummy credentials for HTTP Basic Auth
|
29
|
+
auth = HTTPBasicAuth("user", "password")
|
30
|
+
|
31
|
+
# Add a custom header, for potential use in authorization
|
32
|
+
headers = {"x-jhub-user": payload["requester"]}
|
33
|
+
|
34
|
+
response = requests.post(url=url, json=payload, headers=headers, auth=auth)
|
35
|
+
|
36
|
+
# Print response
|
37
|
+
print(f"Status code: {response.status_code}")
|
38
|
+
|
39
|
+
try:
|
40
|
+
print(f"Response: {response.json()}")
|
41
|
+
except ValueError:
|
42
|
+
print(f"Response: {response.text}")
|
@@ -1,8 +1,9 @@
|
|
1
|
+
import asyncio
|
1
2
|
import os
|
2
|
-
import
|
3
|
+
from typing import Optional
|
4
|
+
|
3
5
|
import httpx
|
4
|
-
import
|
5
|
-
import nest_asyncio
|
6
|
+
import nest_asyncio # type: ignore
|
6
7
|
|
7
8
|
# Apply nest_asyncio for environments like Jupyter
|
8
9
|
nest_asyncio.apply()
|
@@ -13,33 +14,37 @@ class TokenValidationError(Exception):
|
|
13
14
|
Custom exception raised when the token validation fails.
|
14
15
|
"""
|
15
16
|
|
16
|
-
def __init__(self, message=None):
|
17
|
+
def __init__(self, message: Optional[str] = None):
|
17
18
|
"""
|
18
19
|
Initialize the exception with an optional message.
|
19
20
|
|
20
21
|
Args:
|
21
22
|
message (str, optional): The error message to display. Defaults to None.
|
22
23
|
"""
|
24
|
+
|
23
25
|
super().__init__(message)
|
24
26
|
|
25
27
|
|
26
|
-
def get_credentials():
|
28
|
+
def get_credentials() -> dict[str, str]:
|
27
29
|
"""
|
28
30
|
Fetch the username and password from environment variables.
|
29
31
|
|
30
32
|
Returns:
|
31
33
|
dict: A dictionary containing 'username' and 'password'.
|
32
34
|
"""
|
35
|
+
|
33
36
|
username = os.getenv("user_name_student")
|
34
37
|
password = os.getenv("keys_student")
|
38
|
+
|
35
39
|
if not username or not password:
|
36
40
|
raise ValueError(
|
37
|
-
"Environment
|
41
|
+
"Environment variable 'user_name_student' or 'keys_student' not set"
|
38
42
|
)
|
43
|
+
|
39
44
|
return {"username": username, "password": password}
|
40
45
|
|
41
46
|
|
42
|
-
async def async_validate_token(token: str = None) -> None:
|
47
|
+
async def async_validate_token(token: Optional[str] = None) -> None:
|
43
48
|
"""
|
44
49
|
Asynchronously validate a token by making a GET request to the validation endpoint.
|
45
50
|
|
@@ -53,19 +58,22 @@ async def async_validate_token(token: str = None) -> None:
|
|
53
58
|
None: If the token is valid, the function will pass silently.
|
54
59
|
"""
|
55
60
|
|
61
|
+
# First, check if token is provided as an argument
|
56
62
|
if token is not None:
|
57
63
|
os.environ["TOKEN"] = token
|
58
64
|
|
65
|
+
# Next, check if token is available in environment variables
|
59
66
|
if token is None:
|
60
67
|
token = os.getenv("TOKEN", None)
|
61
68
|
|
69
|
+
# Otherwise, raise an error
|
62
70
|
if token is None:
|
63
71
|
raise TokenValidationError("No token provided")
|
64
72
|
|
65
73
|
# Fetch the endpoint URL
|
66
74
|
base_url = os.getenv("DB_URL")
|
67
75
|
if not base_url:
|
68
|
-
raise ValueError("Environment variable 'DB_URL'
|
76
|
+
raise ValueError("Environment variable 'DB_URL' not set")
|
69
77
|
endpoint = f"{base_url}validate-token/{token}"
|
70
78
|
|
71
79
|
# Get credentials
|
@@ -73,17 +81,12 @@ async def async_validate_token(token: str = None) -> None:
|
|
73
81
|
username = credentials["username"]
|
74
82
|
password = credentials["password"]
|
75
83
|
|
76
|
-
|
77
|
-
auth_header = (
|
78
|
-
f"Basic {base64.b64encode(f'{username}:{password}'.encode()).decode()}"
|
79
|
-
)
|
84
|
+
basic_auth = httpx.BasicAuth(username=username, password=password)
|
80
85
|
|
81
|
-
# Make
|
86
|
+
# Make GET request
|
82
87
|
async with httpx.AsyncClient() as client:
|
83
88
|
try:
|
84
|
-
response = await client.get(
|
85
|
-
endpoint, headers={"Authorization": auth_header}, timeout=10
|
86
|
-
)
|
89
|
+
response = await client.get(url=endpoint, auth=basic_auth, timeout=10)
|
87
90
|
|
88
91
|
if response.status_code == 200:
|
89
92
|
# If the response is 200, the token is valid
|
@@ -103,7 +106,7 @@ async def async_validate_token(token: str = None) -> None:
|
|
103
106
|
raise TokenValidationError(f"An unexpected error occurred: {e}")
|
104
107
|
|
105
108
|
|
106
|
-
def validate_token(token: str = None) -> None:
|
109
|
+
def validate_token(token: Optional[str] = None) -> None:
|
107
110
|
"""
|
108
111
|
Synchronous wrapper for the `async_validate_token` function.
|
109
112
|
|
@@ -131,8 +134,9 @@ def validate_token(token: str = None) -> None:
|
|
131
134
|
# Example usage
|
132
135
|
if __name__ == "__main__":
|
133
136
|
token = "test"
|
137
|
+
|
134
138
|
try:
|
135
139
|
validate_token(token)
|
136
|
-
print("Token is valid
|
140
|
+
print("Token is valid")
|
137
141
|
except TokenValidationError as e:
|
138
142
|
print(f"Token validation failed: {e}")
|
@@ -23,7 +23,7 @@ class ReadingPython:
|
|
23
23
|
|
24
24
|
# Dynamically assign attributes based on keys, with default values from responses
|
25
25
|
for num in range(len(options["lines_to_comment"]) + options["n_rows"]):
|
26
|
-
key = f"q{question_number}_{num+1}"
|
26
|
+
key = f"q{question_number}_{num + 1}"
|
27
27
|
|
28
28
|
# Dynamically assign the value from the responses file for persistence
|
29
29
|
if num < len(options["lines_to_comment"]):
|
@@ -57,7 +57,7 @@ class ReadingPython:
|
|
57
57
|
line: pn.widgets.Select(
|
58
58
|
options=shuffle_options(options["comments_options"], seed),
|
59
59
|
name=f"Line {line}:",
|
60
|
-
value=getattr(self, f"q{question_number}_{i_comments+1}"),
|
60
|
+
value=getattr(self, f"q{question_number}_{i_comments + 1}"),
|
61
61
|
width=600,
|
62
62
|
)
|
63
63
|
for i_comments, line in enumerate(options["lines_to_comment"])
|
@@ -97,13 +97,13 @@ class ReadingPython:
|
|
97
97
|
# Function to create a row with dropdowns
|
98
98
|
def create_row(step: int) -> pn.Row:
|
99
99
|
widgets_list = [
|
100
|
-
pn.pane.HTML(f"Step {step+1}", width=150)
|
100
|
+
pn.pane.HTML(f"Step {step + 1}", width=150)
|
101
101
|
if i == 0
|
102
102
|
else pn.widgets.Select(
|
103
103
|
options=dropdown_options[i - 1],
|
104
104
|
value=getattr(
|
105
105
|
self,
|
106
|
-
f
|
106
|
+
f"q{question_number}_{len(options['lines_to_comment']) + step + 1}",
|
107
107
|
)[i - 1],
|
108
108
|
width=150,
|
109
109
|
)
|
@@ -1,47 +0,0 @@
|
|
1
|
-
import requests
|
2
|
-
import os
|
3
|
-
import json
|
4
|
-
|
5
|
-
|
6
|
-
def build_token_payload(token: str, duration: int) -> dict:
|
7
|
-
|
8
|
-
if os.getenv("JUPYTERHUB_USER", None) is None:
|
9
|
-
raise ValueError("JupyterHub user not found")
|
10
|
-
|
11
|
-
# Return the extracted details as a dictionary
|
12
|
-
return {
|
13
|
-
"value": token,
|
14
|
-
"requester": os.getenv("JUPYTERHUB_USER", None),
|
15
|
-
"duration": duration,
|
16
|
-
}
|
17
|
-
|
18
|
-
|
19
|
-
# Need to do for add token as well
|
20
|
-
def add_token(token, duration=20):
|
21
|
-
"""
|
22
|
-
Sends a POST request to add a notebook.
|
23
|
-
"""
|
24
|
-
# Define the URL
|
25
|
-
url = "https://engr-131-api.eastus.cloudapp.azure.com/tokens"
|
26
|
-
|
27
|
-
# Build the payload
|
28
|
-
payload = build_token_payload(token=token, duration=duration)
|
29
|
-
|
30
|
-
# Define HTTP Basic Authentication
|
31
|
-
auth = ("user", "password")
|
32
|
-
|
33
|
-
# Define headers
|
34
|
-
headers = {"Content-Type": "application/json"}
|
35
|
-
|
36
|
-
# Serialize the payload with the custom JSON encoder
|
37
|
-
serialized_payload = json.dumps(payload)
|
38
|
-
|
39
|
-
# Send the POST request
|
40
|
-
response = requests.post(url, data=serialized_payload, headers=headers, auth=auth)
|
41
|
-
|
42
|
-
# Print the response
|
43
|
-
print(f"Status Code: {response.status_code}")
|
44
|
-
try:
|
45
|
-
print(f"Response: {response.json()}")
|
46
|
-
except ValueError:
|
47
|
-
print(f"Response: {response.text}")
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|