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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: PyKubeGrader
3
- Version: 0.2.26
3
+ Version: 0.2.28
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -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=h8PPrXWGFgPWCcrChJo2woqd_XIPMFfYcxgLJ0CWpH8,5360
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=IzzU_jh6SYSA2wtjtlhxa4t9WM8-JqWnWsV8e3UPwdo,20305
8
- pykubegrader/build/build_folder.py,sha256=D8zNP6tFDR0S9zFIqutcPHZGw6px3vpIwnoVsl2LbvE,82949
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=qMUN4aCR9NB_KVfyA13pbqt0nkjDmd0_C04CI-RD1So,1260
17
- pykubegrader/tokens/validate_token.py,sha256=kzXZPCVyYAedSmnzekIdwAhjBY3ZCMUrqOHOKFKAssI,4123
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=_vjUPynqmJe_R4vf-7hVhGnQR726S9GL6qT8bflBXBM,5383
28
+ pykubegrader/widgets_base/reading.py,sha256=xmvN1UIXwk32v9S-JhsXwDc7axPlgpvoxSeM3II8sxY,5393
29
29
  pykubegrader/widgets_base/select.py,sha256=Fw3uFNOIWo1a3CvlzSx23bvi6bSmA3TqutuRbhD4Dp8,2525
30
- PyKubeGrader-0.2.26.dist-info/LICENSE.txt,sha256=YTp-Ewc8Kems8PJEE27KnBPFnZSxoWvSg7nnknzPyYw,1546
31
- PyKubeGrader-0.2.26.dist-info/METADATA,sha256=PHetJ4aQEkMSYy0E6kMfn48RYDy6ZXmesTwd3pHmZfg,2779
32
- PyKubeGrader-0.2.26.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
33
- PyKubeGrader-0.2.26.dist-info/entry_points.txt,sha256=UPMdTT46fQwTYJWtrUwIWIbXbwyOPfNQgBFRa0frWzw,138
34
- PyKubeGrader-0.2.26.dist-info/top_level.txt,sha256=e550Klfze6higFxER1V62fnGOcIgiKRbsrl9CC4UdtQ,13
35
- PyKubeGrader-0.2.26.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.7.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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'max_score = {cell_dict["points"]}\n')
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(".ipynb", "")}_questions.py"
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(".ipynb", "")}_questions.py"
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(".ipynb", "")}_questions.py"
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'{responses["jhub_user"]}@drexel.edu',
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'_{responses["assignment"]}',
141
+ "question": f"_{responses['assignment']}",
142
142
  "responses": responses,
143
143
  }
144
144
 
@@ -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
- if os.getenv("JUPYTERHUB_USER", None) is None:
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
- # Need to do for add token as well
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 add a notebook.
21
+ Sends a POST request to mint a token
23
22
  """
24
- # Define the URL
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
- # Define HTTP Basic Authentication
31
- auth = ("user", "password")
28
+ # Dummy credentials for HTTP Basic Auth
29
+ auth = HTTPBasicAuth("user", "password")
32
30
 
33
- # Define headers
34
- headers = {"Content-Type": "application/json"}
31
+ # Add a custom header, for potential use in authorization
32
+ headers = {"x-jhub-user": payload["requester"]}
35
33
 
36
- # Serialize the payload with the custom JSON encoder
37
- serialized_payload = json.dumps(payload)
34
+ response = requests.post(url=url, json=payload, headers=headers, auth=auth)
38
35
 
39
- # Send the POST request
40
- response = requests.post(url, data=serialized_payload, headers=headers, auth=auth)
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 variables 'user_name_student' or 'keys_student' are not set."
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' is not set.")
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
- # Encode credentials for Basic Authentication
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 the GET request
86
+ # Make GET request
84
87
  async with httpx.AsyncClient() as client:
85
88
  try:
86
- response = await client.get(
87
- endpoint, headers={"Authorization": auth_header}, timeout=10
88
- )
89
-
90
- if response.status_code == 200:
91
- # If the response is 200, the token is valid
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'q{question_number}_{len(options["lines_to_comment"])+step+1}',
106
+ f"q{question_number}_{len(options['lines_to_comment']) + step + 1}",
107
107
  )[i - 1],
108
108
  width=150,
109
109
  )