PyKubeGrader 0.2.26__tar.gz → 0.2.27__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. {pykubegrader-0.2.26/src/PyKubeGrader.egg-info → pykubegrader-0.2.27}/PKG-INFO +2 -2
  2. {pykubegrader-0.2.26 → pykubegrader-0.2.27/src/PyKubeGrader.egg-info}/PKG-INFO +2 -2
  3. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/build/api_notebook_builder.py +6 -5
  4. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/build/build_folder.py +6 -12
  5. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/telemetry.py +2 -2
  6. pykubegrader-0.2.27/src/pykubegrader/tokens/tokens.py +42 -0
  7. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/tokens/validate_token.py +16 -14
  8. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/reading.py +4 -4
  9. pykubegrader-0.2.26/src/pykubegrader/tokens/tokens.py +0 -47
  10. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/.coveragerc +0 -0
  11. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/.github/workflows/main.yml +0 -0
  12. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/.gitignore +0 -0
  13. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/.readthedocs.yml +0 -0
  14. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/AUTHORS.rst +0 -0
  15. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/CHANGELOG.rst +0 -0
  16. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/CONTRIBUTING.rst +0 -0
  17. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/LICENSE.txt +0 -0
  18. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/README.rst +0 -0
  19. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/Makefile +0 -0
  20. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  21. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  22. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/_static/custom.css +0 -0
  23. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/authors.rst +0 -0
  24. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/changelog.rst +0 -0
  25. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/conf.py +0 -0
  26. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/contributing.rst +0 -0
  27. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/index.rst +0 -0
  28. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/license.rst +0 -0
  29. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/readme.rst +0 -0
  30. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/docs/requirements.txt +0 -0
  31. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/examples/.responses.json +0 -0
  32. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/examples/true_false.ipynb +0 -0
  33. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/pyproject.toml +0 -0
  34. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/setup.cfg +0 -0
  35. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/setup.py +0 -0
  36. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/SOURCES.txt +0 -0
  37. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  38. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
  39. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  40. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/requires.txt +0 -0
  41. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  42. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/__init__.py +0 -0
  43. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/build/__init__.py +0 -0
  44. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/build/clean_folder.py +0 -0
  45. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/graders/__init__.py +0 -0
  46. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/graders/late_assignments.py +0 -0
  47. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/initialize.py +0 -0
  48. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/__init__.py +0 -0
  49. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/parse.ipynb +0 -0
  50. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/log_parser/parse.py +0 -0
  51. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/submit/submit_assignment.py +0 -0
  52. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/utils.py +0 -0
  53. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/validate.py +0 -0
  54. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/__init__.py +0 -0
  55. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/multiple_choice.py +0 -0
  56. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/reading_question.py +0 -0
  57. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/select_many.py +0 -0
  58. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/student_info.py +0 -0
  59. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/style.py +0 -0
  60. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/true_false.py +0 -0
  61. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets/types_question.py +0 -0
  62. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/__init__.py +0 -0
  63. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/multi_select.py +0 -0
  64. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/src/pykubegrader/widgets_base/select.py +0 -0
  65. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/tests/conftest.py +0 -0
  66. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/tests/import_test.py +0 -0
  67. {pykubegrader-0.2.26 → pykubegrader-0.2.27}/tox.ini +0 -0
@@ -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.27
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -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.27
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -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"
@@ -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
 
@@ -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,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,17 +81,12 @@ 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
+ response = await client.get(url=endpoint, auth=basic_auth, timeout=10)
89
90
 
90
91
  if response.status_code == 200:
91
92
  # If the response is 200, the token is valid
@@ -133,8 +134,9 @@ def validate_token(token: Optional[str] = None) -> None:
133
134
  # Example usage
134
135
  if __name__ == "__main__":
135
136
  token = "test"
137
+
136
138
  try:
137
139
  validate_token(token)
138
- print("Token is valid.")
140
+ print("Token is valid")
139
141
  except TokenValidationError as e:
140
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'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
  )
@@ -1,47 +0,0 @@
1
- import json
2
- import os
3
-
4
- import requests
5
-
6
-
7
- def build_token_payload(token: str, duration: int) -> dict:
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