PyKubeGrader 0.2.26__py3-none-any.whl → 0.2.28__py3-none-any.whl
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.
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/METADATA +2 -2
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/RECORD +12 -12
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/WHEEL +1 -1
- pykubegrader/build/api_notebook_builder.py +6 -5
- pykubegrader/build/build_folder.py +6 -12
- pykubegrader/telemetry.py +2 -2
- pykubegrader/tokens/tokens.py +14 -19
- pykubegrader/tokens/validate_token.py +21 -27
- pykubegrader/widgets_base/reading.py +4 -4
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/LICENSE.txt +0 -0
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/entry_points.txt +0 -0
- {PyKubeGrader-0.2.26.dist-info → PyKubeGrader-0.2.28.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,11 @@
|
|
1
1
|
pykubegrader/__init__.py,sha256=AoAkdfIjDDZGWLlsIRENNq06L9h46kDGBIE8vRmsCfg,311
|
2
2
|
pykubegrader/initialize.py,sha256=W-O1d9hy6BIYS28x3kcLG50WnNC4196eZwqwzVZdsgU,3902
|
3
|
-
pykubegrader/telemetry.py,sha256=
|
3
|
+
pykubegrader/telemetry.py,sha256=GwKcC8Y8p35L07zwTC7ijR5ux5ceb6LyC_MZp5yBbH8,5360
|
4
4
|
pykubegrader/utils.py,sha256=T3GYnLnTL9VXjTZNPr00sUgMgobQYsNTGwynMyXdvHk,696
|
5
5
|
pykubegrader/validate.py,sha256=2KLSB3wfFZbBh1NGgmrOV073paKAgrQz4AgA6LmCIj4,11076
|
6
6
|
pykubegrader/build/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
7
|
-
pykubegrader/build/api_notebook_builder.py,sha256=
|
8
|
-
pykubegrader/build/build_folder.py,sha256=
|
7
|
+
pykubegrader/build/api_notebook_builder.py,sha256=dlcVrGgsvxnt6GlAUN3e-FrpsPNJKXSHni1fstRCBik,20311
|
8
|
+
pykubegrader/build/build_folder.py,sha256=5jl_TAnxfd9sqWvggJr4i3E7mrEf03cmFDotVrgTZWQ,82697
|
9
9
|
pykubegrader/build/clean_folder.py,sha256=8N0KyL4eXRs0DCw-V_2jR9igtFs_mOFMQufdL6tD-38,1323
|
10
10
|
pykubegrader/graders/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
11
11
|
pykubegrader/graders/late_assignments.py,sha256=_2-rA5RqO0BWY9WAQA_mbCxxPKTOiJOl-byD2CYWaE0,1393
|
@@ -13,8 +13,8 @@ pykubegrader/log_parser/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YB
|
|
13
13
|
pykubegrader/log_parser/parse.ipynb,sha256=5e-9dzUbJk2M8kPP55lVeksm86lSY5ocKfWOP2RSWH0,11921
|
14
14
|
pykubegrader/log_parser/parse.py,sha256=dXzTEOTI6VTRNoHFDAjg6hZUhvB3kHtMb10_KW3NPrw,7641
|
15
15
|
pykubegrader/submit/submit_assignment.py,sha256=UgJXKWw5b8-bRSFnba4iHAyXnujULHcWIask7hKx9ik,3421
|
16
|
-
pykubegrader/tokens/tokens.py,sha256=
|
17
|
-
pykubegrader/tokens/validate_token.py,sha256=
|
16
|
+
pykubegrader/tokens/tokens.py,sha256=TOj0jRun1lWTztKA7KX9SjzIfNQqkwdnbczrFSPWB7Y,1089
|
17
|
+
pykubegrader/tokens/validate_token.py,sha256=SmVfnJDoWaUoiPVlPv_eHqROu5nsdpOshyxAmzozEHU,3790
|
18
18
|
pykubegrader/widgets/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
19
19
|
pykubegrader/widgets/multiple_choice.py,sha256=NjD3-uXSnibpUQ0mO3hRp_O-rynFyl0Dz6IXE4tnCRI,2078
|
20
20
|
pykubegrader/widgets/reading_question.py,sha256=y30_swHwzH8LrT8deWTnxctAAmR8BSxTlXAqMgUrAT4,3031
|
@@ -25,11 +25,11 @@ pykubegrader/widgets/true_false.py,sha256=D45bjRLaAcNzsSlWPgxwTXGVZPE7PER34S30V6
|
|
25
25
|
pykubegrader/widgets/types_question.py,sha256=kZdRRXyFzOtYTmGdC7XWb_2oaxqg1WSuLcQn_sTj6Qc,2300
|
26
26
|
pykubegrader/widgets_base/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
27
27
|
pykubegrader/widgets_base/multi_select.py,sha256=Cl0IN21wXLZuFu-zC65aS9tD4jMfzCRJ2DPjHao5_Ak,4044
|
28
|
-
pykubegrader/widgets_base/reading.py,sha256=
|
28
|
+
pykubegrader/widgets_base/reading.py,sha256=xmvN1UIXwk32v9S-JhsXwDc7axPlgpvoxSeM3II8sxY,5393
|
29
29
|
pykubegrader/widgets_base/select.py,sha256=Fw3uFNOIWo1a3CvlzSx23bvi6bSmA3TqutuRbhD4Dp8,2525
|
30
|
-
PyKubeGrader-0.2.
|
31
|
-
PyKubeGrader-0.2.
|
32
|
-
PyKubeGrader-0.2.
|
33
|
-
PyKubeGrader-0.2.
|
34
|
-
PyKubeGrader-0.2.
|
35
|
-
PyKubeGrader-0.2.
|
30
|
+
PyKubeGrader-0.2.28.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
|
31
|
+
PyKubeGrader-0.2.28.dist-info/METADATA,sha256=23BZ-4RHb8dLTt3xLorKdiC3zqrD1AZRSdvaJRUxkaA,2779
|
32
|
+
PyKubeGrader-0.2.28.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
33
|
+
PyKubeGrader-0.2.28.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
|
34
|
+
PyKubeGrader-0.2.28.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
|
35
|
+
PyKubeGrader-0.2.28.dist-info/RECORD,,
|
@@ -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
|
@@ -791,7 +791,7 @@ class NotebookProcessor:
|
|
791
791
|
data, output_file=solution_path
|
792
792
|
)
|
793
793
|
|
794
|
-
question_path = f"{new_notebook_path.replace(
|
794
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
795
795
|
|
796
796
|
generate_mcq_file(data, output_file=question_path)
|
797
797
|
|
@@ -831,7 +831,7 @@ class NotebookProcessor:
|
|
831
831
|
data, output_file=solution_path
|
832
832
|
)
|
833
833
|
|
834
|
-
question_path = f"{new_notebook_path.replace(
|
834
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
835
835
|
|
836
836
|
generate_tf_file(data, output_file=question_path)
|
837
837
|
|
@@ -869,7 +869,7 @@ class NotebookProcessor:
|
|
869
869
|
data, output_file=solution_path
|
870
870
|
)
|
871
871
|
|
872
|
-
question_path = f"{new_notebook_path.replace(
|
872
|
+
question_path = f"{new_notebook_path.replace('.ipynb', '')}_questions.py"
|
873
873
|
|
874
874
|
generate_select_many_file(data, output_file=question_path)
|
875
875
|
|
@@ -1809,9 +1809,7 @@ def generate_mcq_file(data_dict, output_file="mc_questions.py"):
|
|
1809
1809
|
)
|
1810
1810
|
f.write(" def __init__(self):\n")
|
1811
1811
|
f.write(" super().__init__(\n")
|
1812
|
-
f.write(
|
1813
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1814
|
-
)
|
1812
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1815
1813
|
f.write(" style=MCQ,\n")
|
1816
1814
|
f.write(
|
1817
1815
|
f" question_number={q_value['question number']},\n"
|
@@ -1882,9 +1880,7 @@ def generate_select_many_file(data_dict, output_file="select_many_questions.py")
|
|
1882
1880
|
)
|
1883
1881
|
f.write(" def __init__(self):\n")
|
1884
1882
|
f.write(" super().__init__(\n")
|
1885
|
-
f.write(
|
1886
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1887
|
-
)
|
1883
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1888
1884
|
f.write(" style=MultiSelect,\n")
|
1889
1885
|
f.write(
|
1890
1886
|
f" question_number={q_value['question number']},\n"
|
@@ -1961,9 +1957,7 @@ def generate_tf_file(data_dict, output_file="tf_questions.py"):
|
|
1961
1957
|
)
|
1962
1958
|
f.write(" def __init__(self):\n")
|
1963
1959
|
f.write(" super().__init__(\n")
|
1964
|
-
f.write(
|
1965
|
-
f' title=f"Question{q_value['question number']}: {q_value['title']}",\n'
|
1966
|
-
)
|
1960
|
+
f.write(f' title=f"{q_value["title"]}",\n')
|
1967
1961
|
f.write(" style=TFStyle,\n")
|
1968
1962
|
f.write(
|
1969
1963
|
f" question_number={q_value['question number']},\n"
|
pykubegrader/telemetry.py
CHANGED
@@ -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
|
|
pykubegrader/tokens/tokens.py
CHANGED
@@ -1,46 +1,41 @@
|
|
1
|
-
import json
|
2
1
|
import os
|
3
2
|
|
4
3
|
import requests
|
4
|
+
from requests.auth import HTTPBasicAuth
|
5
5
|
|
6
6
|
|
7
7
|
def build_token_payload(token: str, duration: int) -> dict:
|
8
|
-
|
8
|
+
jhub_user = os.getenv("JUPYTERHUB_USER")
|
9
|
+
if jhub_user is None:
|
9
10
|
raise ValueError("JupyterHub user not found")
|
10
11
|
|
11
|
-
# Return the extracted details as a dictionary
|
12
12
|
return {
|
13
13
|
"value": token,
|
14
|
-
"requester": os.getenv("JUPYTERHUB_USER", None),
|
15
14
|
"duration": duration,
|
15
|
+
"requester": jhub_user,
|
16
16
|
}
|
17
17
|
|
18
18
|
|
19
|
-
|
20
|
-
def add_token(token, duration=20):
|
19
|
+
def add_token(token: str, duration: int = 20) -> None:
|
21
20
|
"""
|
22
|
-
Sends a POST request to
|
21
|
+
Sends a POST request to mint a token
|
23
22
|
"""
|
24
|
-
|
23
|
+
|
25
24
|
url = "https://engr-131-api.eastus.cloudapp.azure.com/tokens"
|
26
25
|
|
27
|
-
# Build the payload
|
28
26
|
payload = build_token_payload(token=token, duration=duration)
|
29
27
|
|
30
|
-
#
|
31
|
-
auth = ("user", "password")
|
28
|
+
# Dummy credentials for HTTP Basic Auth
|
29
|
+
auth = HTTPBasicAuth("user", "password")
|
32
30
|
|
33
|
-
#
|
34
|
-
headers = {"
|
31
|
+
# Add a custom header, for potential use in authorization
|
32
|
+
headers = {"x-jhub-user": payload["requester"]}
|
35
33
|
|
36
|
-
|
37
|
-
serialized_payload = json.dumps(payload)
|
34
|
+
response = requests.post(url=url, json=payload, headers=headers, auth=auth)
|
38
35
|
|
39
|
-
#
|
40
|
-
|
36
|
+
# Print response
|
37
|
+
print(f"Status code: {response.status_code}")
|
41
38
|
|
42
|
-
# Print the response
|
43
|
-
print(f"Status Code: {response.status_code}")
|
44
39
|
try:
|
45
40
|
print(f"Response: {response.json()}")
|
46
41
|
except ValueError:
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import asyncio
|
2
|
-
import base64
|
3
2
|
import os
|
4
3
|
from typing import Optional
|
5
4
|
|
@@ -15,29 +14,33 @@ class TokenValidationError(Exception):
|
|
15
14
|
Custom exception raised when the token validation fails.
|
16
15
|
"""
|
17
16
|
|
18
|
-
def __init__(self, message=None):
|
17
|
+
def __init__(self, message: Optional[str] = None):
|
19
18
|
"""
|
20
19
|
Initialize the exception with an optional message.
|
21
20
|
|
22
21
|
Args:
|
23
22
|
message (str, optional): The error message to display. Defaults to None.
|
24
23
|
"""
|
24
|
+
|
25
25
|
super().__init__(message)
|
26
26
|
|
27
27
|
|
28
|
-
def get_credentials():
|
28
|
+
def get_credentials() -> dict[str, str]:
|
29
29
|
"""
|
30
30
|
Fetch the username and password from environment variables.
|
31
31
|
|
32
32
|
Returns:
|
33
33
|
dict: A dictionary containing 'username' and 'password'.
|
34
34
|
"""
|
35
|
+
|
35
36
|
username = os.getenv("user_name_student")
|
36
37
|
password = os.getenv("keys_student")
|
38
|
+
|
37
39
|
if not username or not password:
|
38
40
|
raise ValueError(
|
39
|
-
"Environment
|
41
|
+
"Environment variable 'user_name_student' or 'keys_student' not set"
|
40
42
|
)
|
43
|
+
|
41
44
|
return {"username": username, "password": password}
|
42
45
|
|
43
46
|
|
@@ -55,19 +58,22 @@ async def async_validate_token(token: Optional[str] = None) -> None:
|
|
55
58
|
None: If the token is valid, the function will pass silently.
|
56
59
|
"""
|
57
60
|
|
61
|
+
# First, check if token is provided as an argument
|
58
62
|
if token is not None:
|
59
63
|
os.environ["TOKEN"] = token
|
60
64
|
|
65
|
+
# Next, check if token is available in environment variables
|
61
66
|
if token is None:
|
62
67
|
token = os.getenv("TOKEN", None)
|
63
68
|
|
69
|
+
# Otherwise, raise an error
|
64
70
|
if token is None:
|
65
71
|
raise TokenValidationError("No token provided")
|
66
72
|
|
67
73
|
# Fetch the endpoint URL
|
68
74
|
base_url = os.getenv("DB_URL")
|
69
75
|
if not base_url:
|
70
|
-
raise ValueError("Environment variable 'DB_URL'
|
76
|
+
raise ValueError("Environment variable 'DB_URL' not set")
|
71
77
|
endpoint = f"{base_url}validate-token/{token}"
|
72
78
|
|
73
79
|
# Get credentials
|
@@ -75,30 +81,17 @@ async def async_validate_token(token: Optional[str] = None) -> None:
|
|
75
81
|
username = credentials["username"]
|
76
82
|
password = credentials["password"]
|
77
83
|
|
78
|
-
|
79
|
-
auth_header = (
|
80
|
-
f"Basic {base64.b64encode(f'{username}:{password}'.encode()).decode()}"
|
81
|
-
)
|
84
|
+
basic_auth = httpx.BasicAuth(username=username, password=password)
|
82
85
|
|
83
|
-
# Make
|
86
|
+
# Make GET request
|
84
87
|
async with httpx.AsyncClient() as client:
|
85
88
|
try:
|
86
|
-
response = await client.get(
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
return # Pass silently
|
93
|
-
elif response.status_code == 404:
|
94
|
-
# If the response is 404, the token is invalid
|
95
|
-
detail = response.json().get("detail", "Token not found")
|
96
|
-
raise TokenValidationError(detail)
|
97
|
-
else:
|
98
|
-
# Handle unexpected status codes
|
99
|
-
raise TokenValidationError(
|
100
|
-
f"Unexpected response code: {response.status_code}"
|
101
|
-
)
|
89
|
+
response = await client.get(url=endpoint, auth=basic_auth, timeout=10)
|
90
|
+
response.raise_for_status()
|
91
|
+
return
|
92
|
+
except httpx.HTTPStatusError as e:
|
93
|
+
detail = e.response.json().get("detail", e.response.text)
|
94
|
+
raise TokenValidationError(detail)
|
102
95
|
except httpx.RequestError as e:
|
103
96
|
raise TokenValidationError(f"Request failed: {e}")
|
104
97
|
except Exception as e:
|
@@ -133,8 +126,9 @@ def validate_token(token: Optional[str] = None) -> None:
|
|
133
126
|
# Example usage
|
134
127
|
if __name__ == "__main__":
|
135
128
|
token = "test"
|
129
|
+
|
136
130
|
try:
|
137
131
|
validate_token(token)
|
138
|
-
print("Token is valid
|
132
|
+
print("Token is valid")
|
139
133
|
except TokenValidationError as e:
|
140
134
|
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
|
)
|
File without changes
|
File without changes
|
File without changes
|