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.
- {pykubegrader-0.2.5/src/PyKubeGrader.egg-info → pykubegrader-0.2.7}/PKG-INFO +2 -1
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/setup.cfg +1 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7/src/PyKubeGrader.egg-info}/PKG-INFO +2 -1
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/SOURCES.txt +1 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/requires.txt +1 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/build_folder.py +85 -3
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/initialize.py +6 -2
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/parse.ipynb +42 -7
- pykubegrader-0.2.7/src/pykubegrader/submit/submit_assignment.py +80 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.coveragerc +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.github/workflows/main.yml +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.gitignore +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/.readthedocs.yml +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/AUTHORS.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/CHANGELOG.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/CONTRIBUTING.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/LICENSE.txt +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/README.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/Makefile +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/Drexel_blue_Logo_square_Dark.png +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/Drexel_blue_Logo_square_Light.png +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/_static/custom.css +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/authors.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/changelog.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/conf.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/contributing.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/index.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/license.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/readme.rst +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/docs/requirements.txt +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/examples/.responses.json +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/examples/true_false.ipynb +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/pyproject.toml +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/setup.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/dependency_links.txt +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/entry_points.txt +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/not-zip-safe +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/PyKubeGrader.egg-info/top_level.txt +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/api_notebook_builder.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/build/clean_folder.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/graders/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/graders/late_assignments.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/log_parser/parse.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/telemetry.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/utils.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/validate.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/multiple_choice.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/reading_question.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/select_many.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/student_info.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/style.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/true_false.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets/types_question.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/__init__.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/multi_select.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/reading.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/src/pykubegrader/widgets_base/select.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/tests/conftest.py +0 -0
- {pykubegrader-0.2.5 → pykubegrader-0.2.7}/tests/import_test.py +0 -0
- {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.
|
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"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: PyKubeGrader
|
3
|
-
Version: 0.2.
|
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
|
@@ -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:
|
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
|
-
"
|
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
|
-
"
|
201
|
-
|
202
|
-
|
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
|
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
|