dlai-grader 1.22.2__py3-none-any.whl → 2.0b2__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.

Potentially problematic release.


This version of dlai-grader might be problematic. Click here for more details.

dlai_grader/__init__.py CHANGED
@@ -6,6 +6,6 @@ from . import grading
6
6
  from . import types
7
7
 
8
8
 
9
- __version__ = "1.22.2"
9
+ __version__ = "2.0b2"
10
10
  __author__ = "Andres Zarta"
11
11
  __credits__ = "DeepLearning.AI"
dlai_grader/config.py CHANGED
@@ -2,12 +2,11 @@ import os
2
2
  import re
3
3
  import csv
4
4
  from dataclasses import dataclass
5
- from typing import Dict
6
5
 
7
6
 
8
7
  def parse_conf(
9
8
  path: str = "./.conf",
10
- ) -> Dict[str, str]:
9
+ ) -> dict[str, str]:
11
10
  """Parses variables from .conf file
12
11
 
13
12
  Args:
@@ -45,10 +44,9 @@ class Config:
45
44
  submission_workdir: str = "./submission/"
46
45
  solution_workdir: str = "./solution/"
47
46
  solution_file: str = "solution.ipynb"
48
- solution_file_path: str = os.path.join(solution_workdir, solution_file)
47
+ solution_file_path: str = ""
49
48
  submission_file: str = "submission.ipynb"
50
- submission_file_path: str = os.path.join(submission_workdir, submission_file)
51
- feedback_file_path: str = "/shared/feedback.json"
49
+ submission_file_path: str = ""
52
50
  part_id: str = get_part_id()
53
51
  latest_version: str = (
54
52
  parse_conf()["GRADER_VERSION"] if os.path.exists("./.conf") else ""
@@ -56,3 +54,12 @@ class Config:
56
54
  assignment_name: str = (
57
55
  parse_conf()["ASSIGNMENT_NAME"] if os.path.exists("./.conf") else ""
58
56
  )
57
+
58
+ def __post_init__(self):
59
+ # This is where we set the file paths after the instance variables are initialized
60
+ self.solution_file_path = os.path.join(
61
+ self.solution_workdir, self.solution_file
62
+ )
63
+ self.submission_file_path = os.path.join(
64
+ self.submission_workdir, self.submission_file
65
+ )
dlai_grader/grading.py CHANGED
@@ -1,75 +1,96 @@
1
1
  from dataclasses import dataclass
2
2
  from functools import wraps
3
- from typing import Any, Callable, List, Tuple, Union
3
+ from typing import Any, Callable
4
4
  from types import ModuleType
5
5
 
6
6
 
7
7
  @dataclass
8
8
  class LearnerSubmission:
9
- """Class that represents a file from the learner. Useful when grading does not depend on the notebook"""
9
+ """Class that represents a file from the learner. Useful when grading does not depend on the notebook."""
10
10
 
11
11
  submission: Any = None
12
12
 
13
13
 
14
- learner_submission = Union[ModuleType, LearnerSubmission]
14
+ learner_submission = ModuleType | LearnerSubmission
15
15
 
16
16
 
17
17
  @dataclass
18
18
  class test_case:
19
- """Class that represents a test case"""
19
+ """Class that represents a test case."""
20
20
 
21
21
  msg: str = ""
22
22
  want: Any = None
23
23
  got: Any = None
24
24
  failed: bool = False
25
25
 
26
+ def fail(self):
27
+ """Sets the failed attribute to True."""
28
+ self.failed = True
29
+
26
30
 
27
31
  @dataclass
28
32
  class aggregated_test_case:
29
- """Class that represents an aggregated collection of test cases"""
33
+ """Class that represents an aggregated collection of test cases."""
30
34
 
31
35
  test_name: str
32
- tests: List[test_case]
36
+ tests: list[test_case]
37
+
33
38
 
39
+ def compute_grading_score(test_cases: list[test_case]) -> tuple[float, str]:
40
+ """
41
+ Computes the score based on the number of failed and total cases.
34
42
 
35
- def compute_grading_score(
36
- test_cases: List[test_case],
37
- ) -> Tuple[float, str]:
38
- """Computes the score based on the number of failed and total cases.
39
43
  Args:
40
44
  test_cases (List): Test cases.
45
+
41
46
  Returns:
42
47
  Tuple[float, str]: The grade and feedback message.
43
- """
44
48
 
49
+ """
45
50
  num_cases = len(test_cases)
46
51
  if num_cases == 0:
47
- return (
48
- 0.0,
49
- "The grader was unable to generate test cases for your implementation. This suggests a bug with your code, please revise your solution and try again.",
50
- )
52
+ msg = "The grader was unable to generate test cases for your implementation. This suggests a bug with your code, please revise your solution and try again."
53
+ return 0.0, msg
51
54
 
52
55
  failed_cases = [t for t in test_cases if t.failed]
53
56
  score = 1.0 - len(failed_cases) / num_cases
54
- feedback_msg = "All tests passed! Congratulations!"
57
+ score = round(score, 2)
58
+
59
+ if not failed_cases:
60
+ msg = "All tests passed! ✅ Congratulations! 🎉"
61
+ return 1.0, msg
62
+
63
+ feedback_msg = ""
64
+ # if print_passed_tests:
65
+ # passed_cases = [t for t in test_cases if not t.failed]
66
+ # for passed_case in passed_cases:
67
+ # feedback_msg += f"✅ {passed_case.msg}.\n"
55
68
 
56
69
  if failed_cases:
57
- feedback_msg = ""
58
70
  for failed_case in failed_cases:
59
- feedback_msg += f"Failed test case: {failed_case.msg}.\nExpected:\n{failed_case.want},\nbut got:\n{failed_case.got}.\n\n"
60
- return round(score, 2), feedback_msg
71
+ feedback_msg += f" {failed_case.msg}.\n"
72
+
73
+ if failed_case.want:
74
+ feedback_msg += f"Expected:\n{failed_case.want}\n"
75
+
76
+ if failed_case.got:
77
+ feedback_msg += f"but got:\n{failed_case.got}\n\n"
61
78
 
62
79
  return score, feedback_msg
63
80
 
64
81
 
65
82
  def compute_aggregated_grading_score(
66
- aggregated_test_cases: List[aggregated_test_case],
67
- ) -> Tuple[float, str]:
68
- """Computes the score based on the number of failed and total cases.
83
+ aggregated_test_cases: list[aggregated_test_case],
84
+ ) -> tuple[float, str]:
85
+ """
86
+ Computes the score based on the number of failed and total cases.
87
+
69
88
  Args:
70
89
  aggregated_test_cases (List): Aggregated test cases for every part.
90
+
71
91
  Returns:
72
92
  Tuple[float, str]: The grade and feedback message.
93
+
73
94
  """
74
95
  scores = []
75
96
  msgs = []
@@ -98,11 +119,14 @@ def compute_aggregated_grading_score(
98
119
  def object_to_grade(
99
120
  origin_module: learner_submission,
100
121
  *attr_names: str,
101
- ) -> Callable[[Callable[[Any], List[test_case]]], Callable[[Any], List[test_case]]]:
102
- """Used as a parameterized decorator to get any number of attributes from a module.
122
+ ) -> Callable[[Callable[[Any], list[test_case]]], Callable[[Any], list[test_case]]]:
123
+ """
124
+ Used as a parameterized decorator to get any number of attributes from a module.
125
+
103
126
  Args:
104
127
  origin_module (ModuleType): A module.
105
- attrs_name (Tuple[str, ...]): Names of the attributes to extract from the module.
128
+ *attrs_name (Tuple[str, ...]): Names of the attributes to extract from the module.
129
+
106
130
  """
107
131
 
108
132
  def middle(func):
@@ -120,11 +144,13 @@ def object_to_grade(
120
144
  return middle
121
145
 
122
146
 
123
- def print_feedback(test_cases: List[test_case]) -> None:
124
- """Prints feedback of public unit tests within notebook.
147
+ def print_feedback(test_cases: list[test_case]) -> None:
148
+ """
149
+ Prints feedback of public unit tests within notebook.
125
150
 
126
151
  Args:
127
152
  test_cases (List[test_case]): List of public test cases.
153
+
128
154
  """
129
155
  failed_cases = [t for t in test_cases if t.failed]
130
156
  feedback_msg = "\033[92m All tests passed!"
@@ -137,16 +163,18 @@ def print_feedback(test_cases: List[test_case]) -> None:
137
163
  print(feedback_msg)
138
164
 
139
165
 
140
- def graded_obj_missing(test_cases: List[test_case]) -> bool:
141
- """Check if the object to grade was found in the learned module.
166
+ def graded_obj_missing(test_cases: list[test_case]) -> bool:
167
+ """
168
+ Check if the object to grade was found in the learned module.
142
169
 
143
170
  Args:
144
171
  test_cases (List[test_case]): List of test cases.
145
172
 
146
173
  Returns:
147
174
  bool: True if object is missing. False otherwise.
175
+
148
176
  """
149
- if len(test_cases) == 1 and test_cases[0].got == type(None):
177
+ if len(test_cases) == 1 and test_cases[0].got is type(None):
150
178
  return True
151
179
 
152
180
  return False
dlai_grader/io.py CHANGED
@@ -1,27 +1,30 @@
1
- import os
2
1
  import json
2
+ import os
3
3
  import shutil
4
- import tarfile
5
- import nbformat
6
- import jupytext
7
4
  import subprocess
5
+ import tarfile
6
+ from contextlib import contextmanager, redirect_stderr, redirect_stdout
8
7
  from os import devnull
8
+ from pathlib import Path
9
9
  from textwrap import dedent
10
10
  from zipfile import ZipFile
11
+
12
+ import jupytext
13
+ import nbformat
11
14
  from nbformat.notebooknode import NotebookNode
12
- from contextlib import contextmanager, redirect_stderr, redirect_stdout
15
+
13
16
  from .notebook import (
14
17
  add_metadata_all_code_cells,
15
18
  add_metadata_code_cells_without_pattern,
16
- tag_code_cells,
17
19
  solution_to_learner_format,
20
+ tag_code_cells,
18
21
  )
19
22
  from .templates import load_templates
20
23
 
21
24
 
22
25
  @contextmanager
23
26
  def suppress_stdout_stderr():
24
- """A context manager that redirects stdout and stderr to devnull"""
27
+ """A context manager that redirects stdout and stderr to devnull."""
25
28
  with open(devnull, "w") as fnull:
26
29
  with redirect_stderr(fnull) as err, redirect_stdout(fnull) as out:
27
30
  yield (err, out)
@@ -30,11 +33,15 @@ def suppress_stdout_stderr():
30
33
  def read_notebook(
31
34
  path: str,
32
35
  ) -> NotebookNode:
33
- """Reads a notebook found in the given path and returns a serialized version.
36
+ """
37
+ Reads a notebook found in the given path and returns a serialized version.
38
+
34
39
  Args:
35
40
  path (str): Path of the notebook file to read.
41
+
36
42
  Returns:
37
43
  NotebookNode: Representation of the notebook following nbformat convention.
44
+
38
45
  """
39
46
  return nbformat.read(path, as_version=nbformat.NO_CONVERT)
40
47
 
@@ -42,10 +49,12 @@ def read_notebook(
42
49
  def tag_notebook(
43
50
  path: str,
44
51
  ) -> None:
45
- """Adds 'graded' tag to all code cells of a notebook.
52
+ """
53
+ Adds 'graded' tag to all code cells of a notebook.
46
54
 
47
55
  Args:
48
56
  path (str): Path to the notebook.
57
+
49
58
  """
50
59
  nb = read_notebook(path)
51
60
  nb = tag_code_cells(nb)
@@ -53,10 +62,12 @@ def tag_notebook(
53
62
 
54
63
 
55
64
  def undeletable_notebook(path: str) -> None:
56
- """Makes all code cells of a notebook non-deletable.
65
+ """
66
+ Makes all code cells of a notebook non-deletable.
57
67
 
58
68
  Args:
59
69
  path (str): Path to the notebook.
70
+
60
71
  """
61
72
  nb = read_notebook(path)
62
73
  nb = add_metadata_all_code_cells(nb, {"deletable": False})
@@ -64,13 +75,17 @@ def undeletable_notebook(path: str) -> None:
64
75
 
65
76
 
66
77
  def uneditable_notebook(path: str) -> None:
67
- """Makes all non-graded code cells of a notebook non-editable.
78
+ """
79
+ Makes all non-graded code cells of a notebook non-editable.
68
80
 
69
81
  Args:
70
82
  path (str): Path to the notebook.
83
+
71
84
  """
72
85
  nb = read_notebook(path)
73
- nb = add_metadata_code_cells_without_pattern(nb, {"editable": False}, ignore_pattern="^# EDITABLE")
86
+ nb = add_metadata_code_cells_without_pattern(
87
+ nb, {"editable": False}, ignore_pattern="^# EDITABLE"
88
+ )
74
89
  jupytext.write(nb, path)
75
90
 
76
91
 
@@ -79,12 +94,14 @@ def extract_tar(
79
94
  destination: str,
80
95
  post_cleanup: bool = True,
81
96
  ) -> None:
82
- """Extracts a tar file unto the desired destination.
97
+ """
98
+ Extracts a tar file unto the desired destination.
83
99
 
84
100
  Args:
85
101
  file_path (str): Path to tar file.
86
102
  destination (str): Path where to save uncompressed files.
87
103
  post_cleanup (bool, optional): If true, deletes the compressed tar file. Defaults to True.
104
+
88
105
  """
89
106
  with tarfile.open(file_path, "r") as my_tar:
90
107
  my_tar.extractall(destination)
@@ -118,14 +135,16 @@ def send_feedback(
118
135
  feedback_path: str = "/shared/feedback.json",
119
136
  err: bool = False,
120
137
  ) -> None:
121
- """Sends feedback to the learner.
138
+ """
139
+ Sends feedback to the learner.
140
+
122
141
  Args:
123
142
  score (float): Grading score to show on Coursera for the assignment.
124
143
  msg (str): Message providing additional feedback.
125
144
  feedback_path (str): Path where the json feedback will be saved. Defaults to /shared/feedback.json
126
145
  err (bool, optional): True if there was an error while grading. Defaults to False.
127
- """
128
146
 
147
+ """
129
148
  post = {"fractionalScore": score, "feedback": msg}
130
149
  print(json.dumps(post))
131
150
 
@@ -166,7 +185,7 @@ def update_grader_version() -> str:
166
185
 
167
186
  new_lines = []
168
187
  for l in lines:
169
- if ("GRADER_VERSION" in l) and (not "TAG_ID" in l):
188
+ if ("GRADER_VERSION" in l) and ("TAG_ID" not in l):
170
189
  _, v = l.split("=")
171
190
  num_v = int(v)
172
191
  new_v = num_v + 1
@@ -227,13 +246,23 @@ def init_grader() -> None:
227
246
  write_file_from_template("./Makefile", template_dict["makefile"])
228
247
  write_file_from_template("./.conf", template_dict["conf"])
229
248
  write_file_from_template("./entry.py", template_dict["entry_py"])
249
+ # write_file_from_template(
250
+ # "./copy_assignment_to_submission.sh",
251
+ # template_dict["copy_assignment_to_submission_sh"],
252
+ # )
230
253
  write_file_from_template("./requirements.txt", "dlai-grader")
231
- os.makedirs("data")
254
+ write_file_from_template("./.env", "")
255
+
232
256
  os.makedirs("learner")
233
257
  os.makedirs("mount")
234
- os.makedirs("solution")
235
258
  os.makedirs("submission")
236
259
 
260
+ if "COPY data/ /grader/data/" in template_dict["dockerfile"]:
261
+ os.makedirs("data")
262
+
263
+ if "COPY solution/ /grader/solution/" in template_dict["dockerfile"]:
264
+ os.makedirs("solution")
265
+
237
266
 
238
267
  def generate_learner_version(
239
268
  filename_source: str,
@@ -285,3 +314,24 @@ def grade_parts(
285
314
  subprocess.run(cmd, shell=True, executable="/bin/bash")
286
315
  except Exception as e:
287
316
  print(f"There was an error grading with coursera_autograder. Details: {e}")
317
+
318
+
319
+ def generate_learner_file(
320
+ filename_source: str,
321
+ filename_target: str,
322
+ ) -> None:
323
+ """
324
+ Generates the learning facing version of any file.
325
+
326
+ Args:
327
+ filename_source (str): Path to original notebook.
328
+ filename_target (str): Path where to save reformatted notebook.
329
+
330
+ """
331
+ solution_code = Path(filename_source).read_text()
332
+
333
+ # format the code to replace with placeholders
334
+ fmt_code = solution_to_learner_format(solution_code)
335
+
336
+ # save the learner files
337
+ Path(filename_target).write_text(fmt_code, encoding="utf-8")
dlai_grader/notebook.py CHANGED
@@ -72,8 +72,8 @@ def keep_tagged_cells(
72
72
  filtered_cells = []
73
73
 
74
74
  for cell in notebook["cells"]:
75
- if (not "tags" in cell["metadata"]) or (
76
- not tag in cell["metadata"].get("tags")
75
+ if ("tags" not in cell["metadata"]) or (
76
+ tag not in cell["metadata"].get("tags")
77
77
  ):
78
78
  continue
79
79
  filtered_cells.append(cell)
@@ -147,7 +147,7 @@ def get_named_cells(
147
147
  named_cells = {}
148
148
  for cell in notebook["cells"]:
149
149
  metadata = cell["metadata"]
150
- if not "name" in metadata:
150
+ if "name" not in metadata:
151
151
  continue
152
152
  named_cells.update({metadata.get("name"): cell})
153
153
  return named_cells
@@ -169,12 +169,12 @@ def tag_code_cells(
169
169
 
170
170
  for cell in notebook["cells"]:
171
171
  if cell["cell_type"] == "code":
172
- if not "tags" in cell["metadata"]:
172
+ if "tags" not in cell["metadata"]:
173
173
  cell["metadata"]["tags"] = []
174
174
 
175
175
  tags = cell["metadata"]["tags"]
176
176
 
177
- if not tag in tags:
177
+ if tag not in tags:
178
178
  tags.append(tag)
179
179
  cell["metadata"]["tags"] = tags
180
180
 
@@ -240,17 +240,17 @@ def add_metadata_code_cells_with_pattern(
240
240
  def add_metadata_code_cells_without_pattern(
241
241
  notebook: NotebookNode,
242
242
  metadata: dict,
243
- match_pattern: str = "^# GRADED ",
244
- ignore_pattern: str = None
243
+ match_pattern: str = "^# GRADED",
244
+ ignore_pattern: str = None,
245
245
  ) -> NotebookNode:
246
246
  """Adds metadata to code cells of a notebook that don't match a regexp pattern and aren't ignored by another pattern.
247
-
247
+
248
248
  Args:
249
249
  notebook (NotebookNode): Notebook to filter.
250
250
  metadata (dict): The metadata which should be a key-value pair.
251
251
  match_pattern (str, optional): Pattern to check which cells to add metadata to. Defaults to "^# GRADED ".
252
252
  ignore_pattern (str, optional): Pattern to check which cells to ignore. If a cell matches this pattern, it will be skipped.
253
-
253
+
254
254
  Returns:
255
255
  NotebookNode: The notebook with the new metadata.
256
256
  """
@@ -258,7 +258,7 @@ def add_metadata_code_cells_without_pattern(
258
258
  if cell["cell_type"] == "code":
259
259
  if ignore_pattern and re.search(ignore_pattern, cell["source"]):
260
260
  continue
261
-
261
+
262
262
  if not re.search(match_pattern, cell["source"]):
263
263
  current_metadata = cell.get("metadata", {})
264
264
  current_metadata.update(metadata)
@@ -0,0 +1,54 @@
1
+ .PHONY: sync learner build debug-unsafe debug-safe grade versioning tag undeletable uneditable init upgrade coursera zip
2
+
3
+ include .conf
4
+
5
+ PARTIDS = 123 456
6
+ OS := $(shell uname)
7
+
8
+ sync:
9
+ cp mount/submission.ipynb ../$(ASSIGNMENT_NAME)_Solution.ipynb
10
+ cp learner/$(ASSIGNMENT_NAME).ipynb ../$(ASSIGNMENT_NAME).ipynb
11
+ cp mount/$(UNIT_TESTS_NAME).py ../$(UNIT_TESTS_NAME).py
12
+
13
+ learner:
14
+ dlai_grader --learner --output_notebook=./learner/$(ASSIGNMENT_NAME).ipynb
15
+ rsync -a --exclude="submission.ipynb" --exclude="__pycache__" --exclude=".mypy_cache" ./mount/ ./learner/
16
+
17
+ build:
18
+ docker build -t $(IMAGE_NAME):$(TAG_ID) .
19
+
20
+ debug-unsafe:
21
+ docker run -it --rm --mount type=bind,source=$(PWD)/mount,target=/shared/submission --mount type=bind,source=$(PWD),target=/grader/ --env-file $(PWD)/.env --entrypoint ash $(IMAGE_NAME):$(TAG_ID)
22
+
23
+ debug-safe:
24
+ docker run -it --rm --mount type=bind,source=$(PWD)/mount,target=/shared/submission --env-file $(PWD)/.env --entrypoint ash $(IMAGE_NAME):$(TAG_ID)
25
+
26
+ grade:
27
+ docker run -it --rm --memory=$(HARD_MEMORY) --cpus="$(CPUS)" --memory-reservation=$(SOFT_MEMORY) --mount type=bind,source=$(PWD)/mount,target=/shared/submission --env-file $(PWD)/.env --entrypoint ash $(IMAGE_NAME):$(TAG_ID) -c 'for partId in $(PARTIDS); do export partId=$$partId; echo "Processing part $$partId"; python entry.py; done'
28
+
29
+ versioning:
30
+ dlai_grader --versioning
31
+
32
+ tag:
33
+ dlai_grader --tag
34
+
35
+ undeletable:
36
+ dlai_grader --undeletable
37
+
38
+ uneditable:
39
+ dlai_grader --uneditable
40
+
41
+ init:
42
+ dlai_grader --versioning
43
+ dlai_grader --tag
44
+ dlai_grader --undeletable
45
+ dlai_grader --uneditable
46
+
47
+ upgrade:
48
+ dlai_grader --upgrade
49
+
50
+ coursera:
51
+ dlai_grader --grade --partids="$(PARTIDS)" --docker=$(IMAGE_NAME):$(TAG_ID) --memory=$(MEMORY_LIMIT) --submission=$(SUB_DIR)
52
+
53
+ zip:
54
+ zip -r $(IMAGE_NAME)$(TAG_ID).zip .
@@ -0,0 +1,12 @@
1
+ #!/bin/bash",
2
+ set -euo pipefail
3
+
4
+ Assignment={{ASSIGNMENT_NAME}}
5
+
6
+ SubmissionFile=submission.ipynb
7
+ SubmissionPath=/shared/submission
8
+ SharedDiskPath=/learner_workplace/$UserId/$CourseId/$LessonId
9
+
10
+ # copy synced files (exam image typically sync all files in lesson folder)
11
+ echo "Copy learner submission from $SharedDiskPath/$Assignment to $SubmissionPath/$SubmissionFile"
12
+ cp $SharedDiskPath/$Assignment $SubmissionPath/$SubmissionFile
@@ -0,0 +1,16 @@
1
+ #!/bin/bash",
2
+ set -euo pipefail
3
+
4
+ Assignment={{ASSIGNMENT_NAME}}
5
+ Extra_file={{EXTRA_FILE_NAME}}
6
+
7
+ SubmissionFile=submission.ipynb
8
+ SubmissionPath=/shared/submission
9
+ SharedDiskPath=/learner_workplace/$UserId/$CourseId/$LessonId
10
+
11
+ # copy synced files (exam image typically sync all files in lesson folder)
12
+ echo "Copy learner submission from $SharedDiskPath/$Assignment to $SubmissionPath/$SubmissionFile"
13
+ cp $SharedDiskPath/$Assignment $SubmissionPath/$SubmissionFile
14
+
15
+ echo "Copy learner submission from $SharedDiskPath/$Extra_file to $SubmissionPath/$Extra_file"
16
+ cp $SharedDiskPath/$Extra_file $SubmissionPath/$Extra_file
@@ -0,0 +1,20 @@
1
+ FROM continuumio/miniconda3@sha256:d601a04ea48fd45e60808c7072243d33703d29434d2067816b7f26b0705d889a
2
+
3
+ RUN apk update && apk add libstdc++
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install -r requirements.txt && rm requirements.txt
8
+
9
+ RUN mkdir /grader && \
10
+ mkdir /grader/submission
11
+
12
+ COPY .conf /grader/.conf
13
+ COPY entry.py /grader/entry.py
14
+ COPY grader.py /grader/grader.py
15
+
16
+ RUN chmod a+rwx /grader/
17
+
18
+ WORKDIR /grader/
19
+
20
+ ENTRYPOINT ["python", "entry.py"]
@@ -0,0 +1,21 @@
1
+ FROM continuumio/miniconda3@sha256:d601a04ea48fd45e60808c7072243d33703d29434d2067816b7f26b0705d889a
2
+
3
+ RUN apk update && apk add libstdc++
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install -r requirements.txt && rm requirements.txt
8
+
9
+ RUN mkdir /grader && \
10
+ mkdir /grader/submission
11
+
12
+ COPY .conf /grader/.conf
13
+ COPY solution/ /grader/solution/
14
+ COPY entry.py /grader/entry.py
15
+ COPY grader.py /grader/grader.py
16
+
17
+ RUN chmod a+rwx /grader/
18
+
19
+ WORKDIR /grader/
20
+
21
+ ENTRYPOINT ["python", "entry.py"]
@@ -0,0 +1,21 @@
1
+ FROM continuumio/miniconda3@sha256:d601a04ea48fd45e60808c7072243d33703d29434d2067816b7f26b0705d889a
2
+
3
+ RUN apk update && apk add libstdc++
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install -r requirements.txt && rm requirements.txt
8
+
9
+ RUN mkdir /grader && \
10
+ mkdir /grader/submission
11
+
12
+ COPY .conf /grader/.conf
13
+ COPY data/ /grader/data/
14
+ COPY entry.py /grader/entry.py
15
+ COPY grader.py /grader/grader.py
16
+
17
+ RUN chmod a+rwx /grader/
18
+
19
+ WORKDIR /grader/
20
+
21
+ ENTRYPOINT ["python", "entry.py"]
@@ -0,0 +1,22 @@
1
+ FROM continuumio/miniconda3@sha256:d601a04ea48fd45e60808c7072243d33703d29434d2067816b7f26b0705d889a
2
+
3
+ RUN apk update && apk add libstdc++
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install -r requirements.txt && rm requirements.txt
8
+
9
+ RUN mkdir /grader && \
10
+ mkdir /grader/submission
11
+
12
+ COPY .conf /grader/.conf
13
+ COPY data/ /grader/data/
14
+ COPY solution/ /grader/solution/
15
+ COPY entry.py /grader/entry.py
16
+ COPY grader.py /grader/grader.py
17
+
18
+ RUN chmod a+rwx /grader/
19
+
20
+ WORKDIR /grader/
21
+
22
+ ENTRYPOINT ["python", "entry.py"]