PyKubeGrader 0.2.5__tar.gz → 0.2.7__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. {pykubegrader-0.2.5/src/PyKubeGrader.egg-info → pykubegrader-0.2.7}/PKG-INFO +2 -1
  2. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/setup.cfg +1 -0
  3. {pykubegrader-0.2.5 → pykubegrader-0.2.7/src/PyKubeGrader.egg-info}/PKG-INFO +2 -1
  4. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/SOURCES.txt +1 -0
  5. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/requires.txt +1 -0
  6. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/build_folder.py +85 -3
  7. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/initialize.py +6 -2
  8. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/parse.ipynb +42 -7
  9. pykubegrader-0.2.7/src/pykubegrader/submit/submit_assignment.py +80 -0
  10. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.coveragerc +0 -0
  11. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.github/workflows/main.yml +0 -0
  12. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.gitignore +0 -0
  13. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.readthedocs.yml +0 -0
  14. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/AUTHORS.rst +0 -0
  15. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/CHANGELOG.rst +0 -0
  16. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/CONTRIBUTING.rst +0 -0
  17. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/LICENSE.txt +0 -0
  18. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/README.rst +0 -0
  19. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/Makefile +0 -0
  20. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
  21. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
  22. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/custom.css +0 -0
  23. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/authors.rst +0 -0
  24. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/changelog.rst +0 -0
  25. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/conf.py +0 -0
  26. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/contributing.rst +0 -0
  27. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/index.rst +0 -0
  28. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/license.rst +0 -0
  29. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/readme.rst +0 -0
  30. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/requirements.txt +0 -0
  31. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/examples/.responses.json +0 -0
  32. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/examples/true_false.ipynb +0 -0
  33. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/pyproject.toml +0 -0
  34. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/setup.py +0 -0
  35. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
  36. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
  37. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
  38. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
  39. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/__init__.py +0 -0
  40. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/__init__.py +0 -0
  41. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/api_notebook_builder.py +0 -0
  42. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/clean_folder.py +0 -0
  43. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/graders/__init__.py +0 -0
  44. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/graders/late_assignments.py +0 -0
  45. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/__init__.py +0 -0
  46. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/parse.py +0 -0
  47. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/telemetry.py +0 -0
  48. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/utils.py +0 -0
  49. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/validate.py +0 -0
  50. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/__init__.py +0 -0
  51. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/multiple_choice.py +0 -0
  52. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/reading_question.py +0 -0
  53. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/select_many.py +0 -0
  54. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/student_info.py +0 -0
  55. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/style.py +0 -0
  56. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/true_false.py +0 -0
  57. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/types_question.py +0 -0
  58. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/__init__.py +0 -0
  59. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/multi_select.py +0 -0
  60. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/reading.py +0 -0
  61. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/select.py +0 -0
  62. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/tests/conftest.py +0 -0
  63. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/tests/import_test.py +0 -0
  64. {pykubegrader-0.2.5 → pykubegrader-0.2.7}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -28,6 +28,7 @@ Requires-Dist: types-python-dateutil
28
28
  Requires-Dist: types-pyyaml
29
29
  Requires-Dist: types-requests
30
30
  Requires-Dist: types-setuptools
31
+ Requires-Dist: httpx
31
32
  Provides-Extra: testing
32
33
  Requires-Dist: setuptools; extra == "testing"
33
34
  Requires-Dist: pytest; extra == "testing"
@@ -38,6 +38,7 @@ install_requires =
38
38
  types-pyyaml
39
39
  types-requests
40
40
  types-setuptools
41
+ httpx
41
42
 
42
43
  [options.packages.find]
43
44
  where = src
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyKubeGrader
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Add a short description here!
5
5
  Home-page: https://github.com/pyscaffold/pyscaffold/
6
6
  Author: jagar2
@@ -28,6 +28,7 @@ Requires-Dist: types-python-dateutil
28
28
  Requires-Dist: types-pyyaml
29
29
  Requires-Dist: types-requests
30
30
  Requires-Dist: types-setuptools
31
+ Requires-Dist: httpx
31
32
  Provides-Extra: testing
32
33
  Requires-Dist: setuptools; extra == "testing"
33
34
  Requires-Dist: pytest; extra == "testing"
@@ -46,6 +46,7 @@ src/pykubegrader/graders/late_assignments.py
46
46
  src/pykubegrader/log_parser/__init__.py
47
47
  src/pykubegrader/log_parser/parse.ipynb
48
48
  src/pykubegrader/log_parser/parse.py
49
+ src/pykubegrader/submit/submit_assignment.py
49
50
  src/pykubegrader/widgets/__init__.py
50
51
  src/pykubegrader/widgets/multiple_choice.py
51
52
  src/pykubegrader/widgets/reading_question.py
@@ -13,6 +13,7 @@ types-python-dateutil
13
13
  types-pyyaml
14
14
  types-requests
15
15
  types-setuptools
16
+ httpx
16
17
 
17
18
  [:python_version < "3.8"]
18
19
  importlib-metadata
@@ -1,3 +1,7 @@
1
+ ### Note
2
+
3
+
4
+
1
5
  import argparse
2
6
  import importlib.util
3
7
  import json
@@ -22,7 +26,7 @@ except: # noqa: E722
22
26
  import nbformat
23
27
 
24
28
  from .api_notebook_builder import FastAPINotebookBuilder
25
-
29
+ from typing import Optional
26
30
 
27
31
  @dataclass
28
32
  class NotebookProcessor:
@@ -66,6 +70,7 @@ class NotebookProcessor:
66
70
  self.assignment_type = self.assignment_tag.split("-")[0].lower()
67
71
  week_num = self.assignment_tag.split("-")[-1]
68
72
 
73
+ self.week_num = week_num
69
74
  self.week = f"week_{week_num}"
70
75
 
71
76
  # Define the folder to store solutions and ensure it exists
@@ -155,7 +160,22 @@ class NotebookProcessor:
155
160
 
156
161
  if self.check_if_file_in_folder("assignment_config.yaml"):
157
162
  self.add_assignment()
158
-
163
+
164
+ self.update_initialize_function()
165
+
166
+ def update_initialize_function(self):
167
+
168
+ for key, value in self.total_point_log.items():
169
+
170
+ assignment_tag = f"week{self.week_num}-{self.assignment_type}"
171
+
172
+ update_initialize_assignment(
173
+ notebook_path = os.path.join(self.root_folder, key + '.ipynb'),
174
+ assignment_points= value,
175
+ assignment_tag = assignment_tag,
176
+ )
177
+
178
+
159
179
  def build_payload(self, yaml_content):
160
180
  """
161
181
  Reads YAML content for an assignment and returns Python variables.
@@ -1380,7 +1400,6 @@ def extract_MCQ(ipynb_file):
1380
1400
  print("Invalid JSON in notebook file.")
1381
1401
  return []
1382
1402
 
1383
-
1384
1403
  def check_for_heading(notebook_path, search_strings):
1385
1404
  """
1386
1405
  Checks if a Jupyter notebook contains a heading cell whose source matches any of the given strings.
@@ -1826,6 +1845,69 @@ def replace_cell_source(notebook_path, cell_index, new_source):
1826
1845
  # Save the notebook
1827
1846
  with open(notebook_path, "w", encoding="utf-8") as f:
1828
1847
  nbformat.write(notebook, f)
1848
+
1849
+ def update_initialize_assignment(
1850
+ notebook_path: str,
1851
+ assignment_points: Optional[float] = None,
1852
+ assignment_tag: Optional[str] = None,
1853
+ ) -> None:
1854
+ """
1855
+ Search for a specific line in a Jupyter Notebook and update it with additional input variables.
1856
+
1857
+ Args:
1858
+ notebook_path (str): The path to the Jupyter Notebook file (.ipynb).
1859
+ assignment_points (Optional[float]): The assignment points variable to add (default is None).
1860
+ assignment_tag (Optional[str]): The assignment tag variable to add (default is None).
1861
+
1862
+ Returns:
1863
+ None
1864
+ """
1865
+ # Load the notebook content
1866
+ with open(notebook_path, "r", encoding="utf-8") as file:
1867
+ notebook_data = json.load(file)
1868
+
1869
+ # Pattern to match the specific initialize_assignment line
1870
+ pattern = re.compile(r"responses\s*=\s*initialize_assignment\((.*?)\)")
1871
+
1872
+ # Collect additional variables
1873
+ additional_variables = []
1874
+ if assignment_points is not None:
1875
+ additional_variables.append(f"assignment_points = {assignment_points}")
1876
+ if assignment_tag is not None:
1877
+ additional_variables.append(f"assignment_tag = '{assignment_tag}'")
1878
+
1879
+ # Join additional variables into a string
1880
+ additional_variables_str = ", ".join(additional_variables)
1881
+
1882
+ # Flag to check if any replacements were made
1883
+ updated = False
1884
+
1885
+ # Iterate through notebook cells
1886
+ for cell in notebook_data.get("cells", []):
1887
+ if cell.get("cell_type") == "code": # Only modify code cells
1888
+ source_code = cell.get("source", [])
1889
+ for i, line in enumerate(source_code):
1890
+ match = pattern.search(line)
1891
+ if match:
1892
+ # Extract existing arguments
1893
+ existing_args = match.group(1).strip()
1894
+ # Replace with the updated line
1895
+ if additional_variables_str:
1896
+ updated_line = (
1897
+ f"responses = initialize_assignment({existing_args}, {additional_variables_str})\n"
1898
+ )
1899
+ else:
1900
+ updated_line = f"responses = initialize_assignment({existing_args})\n"
1901
+ source_code[i] = updated_line
1902
+ updated = True
1903
+
1904
+ # If updated, save the notebook
1905
+ if updated:
1906
+ with open(notebook_path, "w", encoding="utf-8") as file:
1907
+ json.dump(notebook_data, file, indent=2)
1908
+ print(f"Notebook '{notebook_path}' has been updated.")
1909
+ else:
1910
+ print(f"No matching lines found in '{notebook_path}'.")
1829
1911
 
1830
1912
 
1831
1913
  def main():
@@ -6,16 +6,18 @@ from pathlib import Path
6
6
  import panel as pn
7
7
  import requests
8
8
  from IPython import get_ipython
9
-
9
+ from typing import Optional
10
10
  from .telemetry import ensure_responses, log_variable, telemetry, update_responses
11
11
 
12
12
 
13
13
  def initialize_assignment(
14
14
  name: str,
15
- week: int,
15
+ week: str,
16
16
  assignment_type: str,
17
17
  url: str = "https://engr-131-api.eastus.cloudapp.azure.com/",
18
18
  verbose: bool = False,
19
+ assignment_points: Optional[float] = None,
20
+ assignment_tag: Optional[str] = None,
19
21
  ) -> dict:
20
22
  """
21
23
  Initialize an assignment in a Jupyter environment.
@@ -75,6 +77,8 @@ def initialize_assignment(
75
77
  except Exception as e:
76
78
  raise Exception(f"Failed to initialize assignment: {e}")
77
79
 
80
+ log_variable("total-points", f"{assignment_tag}, {name}", assignment_points)
81
+
78
82
  print("Assignment successfully initialized")
79
83
  if verbose:
80
84
  print(f"Assignment: {name}")
@@ -1,5 +1,22 @@
1
1
  {
2
2
  "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "# Get the public/private keypair for decryption\n",
10
+ "key_box = get_keybox()"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": null,
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": []
19
+ },
3
20
  {
4
21
  "cell_type": "code",
5
22
  "execution_count": null,
@@ -134,7 +151,7 @@
134
151
  "parser = LogParser(log_lines=log_lines, week_tag=\"week1-readings\")\n",
135
152
  "parser.parse_logs()\n",
136
153
  "parser.calculate_total_scores()\n",
137
- "results = parser.get_results()\n",
154
+ "results = parser.get_results() \n",
138
155
  "\n",
139
156
  "results"
140
157
  ]
@@ -182,6 +199,15 @@
182
199
  "outputs": [],
183
200
  "source": []
184
201
  },
202
+ {
203
+ "cell_type": "code",
204
+ "execution_count": null,
205
+ "metadata": {},
206
+ "outputs": [],
207
+ "source": [
208
+ "type(results[\"student_information\"][\"timestamp\"])"
209
+ ]
210
+ },
185
211
  {
186
212
  "cell_type": "code",
187
213
  "execution_count": null,
@@ -190,16 +216,23 @@
190
216
  "source": [
191
217
  "student_email = results[\"student_information\"][\"username\"]\n",
192
218
  "time_stamp = results[\"student_information\"][\"timestamp\"]\n",
193
- "assignments_graded = results[\"assignment_information\"].keys()\n",
219
+ "\n",
194
220
  "week_num = results[\"week_num\"]\n",
195
221
  "assignment_type = results['assignment_type']\n",
196
222
  "\n",
197
- "\n",
223
+ "assignments_graded = results[\"assignment_information\"].keys()\n",
198
224
  "total_score = 0\n",
199
225
  "for assignment in assignments_graded:\n",
200
- " max_points = results[\"assignment_information\"][assignment][\"max_points\"]\n",
201
- " total_score = results[\"assignment_information\"][assignment][\"total_score\"]\n",
202
- "\n"
226
+ " total_score += results[\"assignment_information\"][assignment][\"total_score\"]"
227
+ ]
228
+ },
229
+ {
230
+ "cell_type": "code",
231
+ "execution_count": null,
232
+ "metadata": {},
233
+ "outputs": [],
234
+ "source": [
235
+ "total_score"
203
236
  ]
204
237
  },
205
238
  {
@@ -217,7 +250,9 @@
217
250
  "execution_count": null,
218
251
  "metadata": {},
219
252
  "outputs": [],
220
- "source": []
253
+ "source": [
254
+ "student_email"
255
+ ]
221
256
  },
222
257
  {
223
258
  "cell_type": "code",
@@ -0,0 +1,80 @@
1
+ import os
2
+ import httpx
3
+ import asyncio
4
+
5
+
6
+ def get_credentials():
7
+ """
8
+ Fetch the username and password from environment variables.
9
+ """
10
+ username = os.getenv("user_name_student")
11
+ password = os.getenv("keys_student")
12
+ if not username or not password:
13
+ raise ValueError(
14
+ "Environment variables 'user_name_student' or 'keys_student' are not set."
15
+ )
16
+ return {"username": username, "password": password}
17
+
18
+
19
+ async def call_score_assignment(
20
+ assignment_title: str, file_path: str = ".output_reduced.log"
21
+ ) -> dict:
22
+ """
23
+ Submit an assignment to the scoring endpoint.
24
+
25
+ Args:
26
+ assignment_title (str): Title of the assignment.
27
+ file_path (str): Path to the log file to upload.
28
+
29
+ Returns:
30
+ dict: JSON response from the server.
31
+ """
32
+ # Fetch the endpoint URL from environment variables
33
+ base_url = os.getenv("DB_URL")
34
+ if not base_url:
35
+ raise ValueError("Environment variable 'DB_URL' is not set.")
36
+ url = f"{base_url}score-assignment"
37
+
38
+ # Get credentials
39
+ credentials = get_credentials()
40
+
41
+ # Send the POST request
42
+ async with httpx.AsyncClient() as client:
43
+ try:
44
+ with open(file_path, "rb") as file:
45
+ response = await client.post(
46
+ url,
47
+ data={"cred": credentials, "assignment_title": assignment_title},
48
+ files={"log_file": file},
49
+ )
50
+
51
+ # Handle the response
52
+ response.raise_for_status() # Raise an exception for HTTP errors
53
+ response_data = response.json()
54
+ return response_data
55
+ except FileNotFoundError:
56
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
57
+ except httpx.RequestError as e:
58
+ raise RuntimeError(f"An error occurred while requesting {url}: {e}")
59
+ except Exception as e:
60
+ raise RuntimeError(f"An unexpected error occurred: {e}")
61
+
62
+
63
+ # Importable function
64
+ def submit_assignment(
65
+ assignment_title: str, file_path: str = ".output_reduced.log"
66
+ ) -> None:
67
+ """
68
+ Synchronous wrapper for the `call_score_assignment` function.
69
+
70
+ Args:
71
+ assignment_title (str): Title of the assignment.
72
+ file_path (str): Path to the log file to upload.
73
+ """
74
+ response = asyncio.run(call_score_assignment(assignment_title, file_path))
75
+ print("Server Response:", response.get("message", "No message in response"))
76
+
77
+
78
+ # Example usage (remove this section if only the function needs to be importable):
79
+ if __name__ == "__main__":
80
+ submit_assignment("Week 1 Assignment", "path/to/your/log_file.txt")
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