assignment-codeval 0.0.4__tar.gz → 0.0.5__tar.gz

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.
Files changed (20) hide show
  1. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/PKG-INFO +1 -1
  2. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/pyproject.toml +1 -1
  3. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/canvas_utils.py +5 -3
  4. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/commons.py +4 -0
  5. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/evaluate.py +5 -1
  6. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/github_connect.py +17 -10
  7. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/submissions.py +16 -9
  8. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/PKG-INFO +1 -1
  9. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/README.md +0 -0
  10. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/setup.cfg +0 -0
  11. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/__init__.py +0 -0
  12. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/cli.py +0 -0
  13. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/convertMD2Html.py +0 -0
  14. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/create_assignment.py +0 -0
  15. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval/file_utils.py +0 -0
  16. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/SOURCES.txt +0 -0
  17. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/dependency_links.txt +0 -0
  18. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/entry_points.txt +0 -0
  19. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/requires.txt +0 -0
  20. {assignment_codeval-0.0.4 → assignment_codeval-0.0.5}/src/assignment_codeval.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assignment-codeval
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: CodEval for evaluating programming assignments
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "assignment-codeval"
7
- version = "0.0.4"
7
+ version = "0.0.5"
8
8
  description = "CodEval for evaluating programming assignments"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -8,7 +8,7 @@ import click
8
8
  from canvasapi import Canvas
9
9
  from canvasapi.current_user import CurrentUser
10
10
 
11
- from assignment_codeval.commons import error, info, errorWithException
11
+ from assignment_codeval.commons import error, info, errorWithException, despace
12
12
 
13
13
  CanvasConnection = NamedTuple('CanvasConnection', [('canvas', Canvas), ('user', CurrentUser)])
14
14
 
@@ -69,6 +69,7 @@ def is_teacher(course):
69
69
 
70
70
  def get_courses(canvas, name: str, is_active=True, is_finished=False):
71
71
  ''' find the courses based on partial match '''
72
+ name = despace(name)
72
73
  courses = [c for c in canvas.get_courses() if is_teacher(c)]
73
74
  now = datetime.datetime.now(datetime.timezone.utc)
74
75
  course_list = []
@@ -79,7 +80,7 @@ def get_courses(canvas, name: str, is_active=True, is_finished=False):
79
80
  continue
80
81
  if is_finished and end < now:
81
82
  continue
82
- if name in c.name:
83
+ if name in despace(c.name):
83
84
  c.start = start
84
85
  c.end = end
85
86
  course_list.append(c)
@@ -88,7 +89,8 @@ def get_courses(canvas, name: str, is_active=True, is_finished=False):
88
89
 
89
90
  @cache
90
91
  def get_assignment(course, assignment_name):
91
- assignments = [a for a in course.get_assignments() if assignment_name.lower() in a.name.lower()]
92
+ assignment_name = despace(assignment_name)
93
+ assignments = [a for a in course.get_assignments() if assignment_name.lower() in despace(a.name).lower()]
92
94
  if len(assignments) == 0:
93
95
  error(f'no assignments found that contain {assignment_name}. options are:')
94
96
  for a in course.get_assignments():
@@ -5,6 +5,10 @@ import time
5
5
  import dataclasses
6
6
 
7
7
 
8
+ def despace(name):
9
+ return name.replace(" ", "_")
10
+
11
+
8
12
  @dataclasses.dataclass(init=True, repr=True, frozen=True)
9
13
  class _Config():
10
14
  """Global configuration object for the CLI"""
@@ -532,7 +532,7 @@ def check_test():
532
532
  try:
533
533
  test_exec.communicate(timeout=timeout_val)
534
534
 
535
- except TimeoutError:
535
+ except subprocess.TimeoutExpired:
536
536
  print(f"Took more than {timeout_val} seconds to run. FAIL")
537
537
  passed = False
538
538
 
@@ -650,6 +650,10 @@ def run_evaluation(codeval_file):
650
650
  """
651
651
  start_time_seconds = time.time()
652
652
 
653
+ # turn off line buffering so that outputs don't get intermixed
654
+ sys.stdout.reconfigure(line_buffering=True)
655
+ sys.stderr.reconfigure(line_buffering=True)
656
+
653
657
  setup()
654
658
 
655
659
  # Count test case total
@@ -2,21 +2,27 @@ import os.path
2
2
  import re
3
3
  import subprocess
4
4
  from configparser import ConfigParser
5
+ from time import sleep
5
6
 
6
7
  import click
7
8
 
8
9
  from assignment_codeval.canvas_utils import get_course, connect_to_canvas, get_assignment
9
- from assignment_codeval.commons import error, debug, info
10
+ from assignment_codeval.commons import error, info, despace
10
11
 
11
12
  HEX_DIGITS = "0123456789abcdefABCDEF"
12
13
 
14
+
13
15
  @click.command()
14
16
  @click.argument("course_name", metavar="COURSE")
15
17
  @click.argument("assignment_name", metavar="ASSIGNMENT")
16
- @click.option("--all-repos", is_flag=True, help="download all repositories, even if they don't have a valid commit hash")
18
+ @click.option("--all-repos", is_flag=True,
19
+ help="download all repositories, even if they don't have a valid commit hash")
17
20
  @click.option("--target-dir", help="directory to download submissions to", default='./submissions', show_default=True)
18
21
  @click.option("--github-field", help="GitHub field name in canvas profile", default="github", show_default=True)
19
- def github_setup_repo(course_name, assignment_name, target_dir, github_field, all_repos):
22
+ @click.option("--clone-delay",
23
+ help="seconds to wait between cloning repos. github will sometimes return an error if you clone to fast.e",
24
+ default=1, show_default=True)
25
+ def github_setup_repo(course_name, assignment_name, target_dir, github_field, all_repos, clone_delay):
20
26
  """
21
27
  Connect to a GitHub repository for a given course and assignment.
22
28
 
@@ -30,7 +36,7 @@ def github_setup_repo(course_name, assignment_name, target_dir, github_field, al
30
36
  parser.config_file = config_file
31
37
  course = get_course(canvas, course_name, True)
32
38
  assignment = get_assignment(course, assignment_name)
33
- submission_dir = os.path.join(target_dir, course.name, assignment.name)
39
+ submission_dir = os.path.join(target_dir, despace(course.name), despace(assignment.name))
34
40
  os.makedirs(submission_dir, exist_ok=True)
35
41
 
36
42
  gh_key = course.name.replace(":", "").replace("=", "")
@@ -53,9 +59,9 @@ def github_setup_repo(course_name, assignment_name, target_dir, github_field, al
53
59
  result_path = f"{ssid_dir}/comments.txt"
54
60
  success_path = f"{ssid_dir}/gh_success.txt"
55
61
  content_path = f"{ssid_dir}/content.txt"
56
- repo_path = os.path.join(ssid_dir, "repo")
57
- if os.path.exists(repo_path):
58
- info(f"skipping {ssid_dir}, repo already exists at {repo_path}")
62
+ submission_path = os.path.join(ssid_dir, "submission")
63
+ if os.path.exists(submission_path):
64
+ info(f"skipping {ssid_dir}, repo already exists at {submission_path}")
59
65
  continue
60
66
  with open(result_path, "w") as fd:
61
67
  content = None
@@ -82,15 +88,16 @@ def github_setup_repo(course_name, assignment_name, target_dir, github_field, al
82
88
  repo_url = f"{gh_repo_prefix}-{gh_id}.git"
83
89
  click.echo(f"Cloning repo for {gh_id} to {ssid_dir}")
84
90
  print(f"cloning {repo_url}", file=fd)
85
- rc = subprocess.run(['git', 'clone', repo_url, repo_path], stdout=fd, stderr=subprocess.STDOUT)
91
+ rc = subprocess.run(['git', 'clone', repo_url, submission_path], stdout=fd, stderr=subprocess.STDOUT)
86
92
  if rc.returncode != 0:
87
93
  error(f"❌ error {rc.returncode} connecting to github repo for {ssid} using {repo_url}")
88
94
  continue
89
- subprocess.run(['git', 'config', 'advice.detachedHead', 'false'], cwd=repo_path)
90
- rc = subprocess.run(['git', 'checkout', content], cwd=repo_path, stdout=fd, stderr=subprocess.STDOUT)
95
+ subprocess.run(['git', 'config', 'advice.detachedHead', 'false'], cwd=submission_path)
96
+ rc = subprocess.run(['git', 'checkout', content], cwd=submission_path, stdout=fd, stderr=subprocess.STDOUT)
91
97
  if rc.returncode != 0:
92
98
  print(f"❌ error {rc.returncode} checking out {content}", file=fd)
93
99
  continue
94
100
  print(f"✅ successfully connected to {repo_url} and checked out {content}", file=fd)
95
101
  with open(success_path, "w") as sfd:
96
102
  print(content, file=sfd)
103
+ sleep(clone_delay)
@@ -12,7 +12,7 @@ import click
12
12
  import requests
13
13
 
14
14
  from assignment_codeval.canvas_utils import connect_to_canvas, get_course, get_assignment
15
- from assignment_codeval.commons import debug, error, info, warn
15
+ from assignment_codeval.commons import debug, error, info, warn, despace
16
16
 
17
17
 
18
18
  @click.command()
@@ -82,7 +82,7 @@ def evaluate_submissions(codeval_dir, submissions_dir):
82
82
  info(f"processing {dirpath}")
83
83
 
84
84
  assignment_name = match.group(2)
85
- repo_dir = os.path.abspath(os.path.join(dirpath, "repo"))
85
+ submission_dir = os.path.abspath(os.path.join(dirpath, "submission"))
86
86
 
87
87
  codeval_file = os.path.join(codeval_dir, f"{assignment_name}.codeval")
88
88
  if not os.path.exists(codeval_file):
@@ -104,22 +104,29 @@ def evaluate_submissions(codeval_dir, submissions_dir):
104
104
  if line.startswith("CD"):
105
105
  assignment_working_dir = os.path.normpath(
106
106
  os.path.join(assignment_working_dir, line.split()[1].strip()))
107
- if not os.path.isdir(os.path.join(repo_dir, assignment_working_dir)):
107
+ if not os.path.isdir(os.path.join(submission_dir, assignment_working_dir)):
108
108
  out = f"{assignment_working_dir} does not exist or is not a directory\n".encode('utf-8')
109
109
  move_to_next_submission = True
110
110
  break
111
111
  if line.startswith("Z"):
112
112
  zipfile = line.split(None, 1)[1]
113
- # unzip into the repo directory
113
+ # unzip into the submission directory
114
114
  with ZipFile(os.path.join(codeval_dir, zipfile)) as zf:
115
- zf.extractall(os.path.join(repo_dir, assignment_working_dir))
115
+ for f in zf.infolist():
116
+ dest_dir = os.path.join(submission_dir, assignment_working_dir)
117
+ zf.extract(f, dest_dir)
118
+ if not f.is_dir():
119
+ perms = f.external_attr >> 16
120
+ if perms:
121
+ os.chmod(os.path.join(dest_dir, f.filename), perms)
122
+
116
123
  if not move_to_next_submission:
117
124
  command = raw_command.replace("EVALUATE", "cd /submissions; assignment-codeval run-evaluation codeval.txt")
118
125
 
119
126
  with TemporaryDirectory("cedir", dir="/var/tmp") as link_dir:
120
- repo_link = os.path.join(link_dir, "submissions")
121
- os.symlink(repo_dir, repo_link)
122
- full_assignment_working_dir = os.path.join(repo_link, assignment_working_dir)
127
+ submission_link = os.path.join(link_dir, "submissions")
128
+ os.symlink(submission_dir, submission_link)
129
+ full_assignment_working_dir = os.path.join(submission_link, assignment_working_dir)
123
130
  shutil.copy(codeval_file, os.path.join(full_assignment_working_dir, "codeval.txt"))
124
131
 
125
132
  command = command.replace("SUBMISSIONS", full_assignment_working_dir)
@@ -168,7 +175,7 @@ def download_submissions(course_name, assignment_name, target_dir, include_comme
168
175
 
169
176
  course = get_course(canvas, course_name)
170
177
  assignment = get_assignment(course, assignment_name)
171
- submission_dir = os.path.join(target_dir, course.name, assignment.name)
178
+ submission_dir = os.path.join(target_dir, despace(course.name), despace(assignment.name))
172
179
  os.makedirs(submission_dir, exist_ok=True)
173
180
 
174
181
  for submission in assignment.get_submissions(include=["submission_comments", "user"]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assignment-codeval
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: CodEval for evaluating programming assignments
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown