def load_file_submissions(self, scripts): # Get instructor control scripts all_scripts = [] for script in scripts: script_file_name, script_file_extension = os.path.splitext(script) # Single Python file if script_file_extension in ('.py', ): with open(script, 'r') as scripts_file: scripts_contents = scripts_file.read() all_scripts.append((script, scripts_contents)) given_submissions = self.config.submissions # If submission is a directory, use it as a directory adjacent to each ics if os.path.isdir(given_submissions): for script, scripts_contents in all_scripts: directory_pattern = given_submissions submission_dir = normalize_path(directory_pattern, script) submission_files = [ os.path.join(submission_dir, sub) for sub in os.listdir(submission_dir) ] subs = get_python_files(submission_files) for main_file, main_code in subs.items(): new_submission = Submission(main_file=main_file, main_code=main_code, instructor_file=script) self.submissions.append( Bundle(self.config, scripts_contents, new_submission)) # Otherwise, if the submission is a single file: # Maybe it's a Progsnap DB file? elif given_submissions.endswith('.db'): for script, scripts_contents in all_scripts: self.load_progsnap(given_submissions, instructor_code=scripts_contents) # Otherwise, must just be a single python file. else: main_file = given_submissions load_error = None try: with open(given_submissions, 'r') as single_submission_file: main_code = single_submission_file.read() except OSError as e: # Okay, file does not exist. Load error gets triggered. main_code = None load_error = e for script, scripts_contents in all_scripts: new_submission = Submission(main_file=main_file, main_code=main_code, instructor_file=script, load_error=load_error) self.submissions.append( Bundle(self.config, scripts_contents, new_submission))
def contextualize_report(submission, filename='answer.py', clear=True, report=MAIN_REPORT): """ Updates the report with the submission. By default, clears out any old information in the report. You can pass in either an actual :py:class:`~pedal.core.submission.Submission` or a string representing the code of the submission. Args: submission (str or Submission): filename (str or None): If the `submission` was not a :py:class:`~pedal.core.submission.Submission`, then this will be used as the filename for the code given in ``submission``. clear (bool): Whether or not to clear the report before attaching the submission. report: The report to attach this feedback to (defaults to the :py:data:`~pedal.core.report.MAIN_REPORT`). """ if not isinstance(submission, Submission): submission = Submission(files={filename: submission}) if clear: report.clear() report.contextualize(submission)
def load_submissions(self): # Use first argument as student submissions given_script = self.config.instructor # ... either a single file if os.path.isfile(given_script): scripts = [given_script] # ... or a directory of files else: scripts = os.listdir(given_script) # Then create submission to run for script in scripts: script_file_name, script_file_extension = os.path.splitext(script) if script_file_extension in ('.py', ): load_error = None try: with open(script, 'r') as scripts_file: scripts_contents = scripts_file.read() except Exception as e: load_error = e new_submission = Submission(main_file="answer.py", main_code=scripts_contents, instructor_file=script, load_error=load_error) self.submissions.append( Bundle(self.config, self.ICS, new_submission))
def load_progsnap(self, path, instructor_code=None): script_file_name, script_file_extension = os.path.splitext(path) if script_file_extension in ('.db', ): with SqlProgSnap2(path, cache=self.config.cache) as progsnap: if self.config.progsnap_profile: progsnap.set_profile(self.config.progsnap_profile) link_filters = {} include_scripts = self.config.include_scripts if include_scripts: link_filters['Assignment'] = {} if include_scripts.startswith('name='): link_filters['Assignment']['X-Name'] = include_scripts[ 5:] elif include_scripts.startswith('id='): link_filters['Assignment'][ 'AssignmentId'] = include_scripts[3:] elif include_scripts.startswith('url='): link_filters['Assignment']['X-URL'] = include_scripts[ 4:] else: link_filters['Assignment']['X-URL'] = include_scripts event_type = self.progsnap_events_map[ self.config.progsnap_events] events = progsnap.get_events( event_filter={'EventType': event_type}, link_filters=link_filters, limit=self.config.limit) if self.config.progsnap_events == 'last': events = [ e for e in {(event['student_email'], event['assignment_name']): event for event in sorted(events, key=lambda e: e['event_id']) }.values() ] for event in events: if instructor_code is None: scripts_contents = event['on_run'] new_submission = Submission( main_file='answer.py', main_code=event['submission_code'].decode('utf-8'), instructor_file='instructor.py', execution=dict(client_timestamp=event['client_timestamp'], event_id=event['event_id']), user=dict(email=event['student_email'], first=event['student_first'], last=event['student_last']), assignment=dict(name=event['assignment_name'], url=event['assignment_url']), ) self.submissions.append( Bundle(self.config, instructor_code, new_submission)) # raise ValueError("TODO: ProgSnap DB files not yet supported") # Progsnap Zip elif script_file_extension in ('.zip', ): raise ValueError("TODO: Zip files not yet supported")
def __init__(self, files=None, main_file='answer.py', main_code=None, user=None, assignment=None, course=None, execution=None, instructor_file='instructor.py', report=MAIN_REPORT): self.report = report report.clear() # Setup any code given as the submission. if isinstance(files, Submission): self.submission = files else: load_error = None if files is None: if main_code is None: if main_file is None: raise ValueError( "files, main_code, and main_file cannot all be None." ) else: main_code = self.load_main(main_file) if isinstance(main_code, Exception): load_error = main_code main_code = "" files = {main_file: main_code} else: if main_file is not None: if main_code is None: main_code = files[main_file] if main_file not in files: files[main_file] = main_code self.submission = Submission(files, main_file, main_code, user, assignment, course, execution, instructor_file, load_error=load_error) # Contextualize report self.report.contextualize(self.submission) self.fields = {}
def test_unittest(self): student_code = dedent(''' x = 0 ''') student = Sandbox() student.run(student_code) student.call('x') self.assertIsNotNone(student.exception) student_code = dedent(''' class Fruit: def __init__(self, name, weight=0): self.name = name self.weight = weight def do_math(a, b): return a + b - 5 def weigh_fruits(fruits): return sum(fruit.weight for fruit in fruits) ''') student.report.contextualize(Submission(main_code=student_code)) student = Sandbox() student.run() result = student.call('do_math', 15, 20) self.assertEqual(result, 30) self.assertEqual(['do_math(15, 20)'], [context.code for context in student.get_context()]) banana = student.call('Fruit', "Banana") self.assertIsInstance(banana, student.data['Fruit']) self.assertEqual(["Fruit('Banana')"], [context.code for context in student.get_context()]) student.start_grouping_context() student.run() orange = student.call("Fruit", "Orange", 30, target="orange") self.assertIsInstance(orange, student.data['Fruit']) student.call("Fruit", "Pineapple", 60, target="pineapple") student.run("fruits = [orange, pineapple]") total_weight = student.call('weigh_fruits', args_locals=["fruits"]) self.assertEqual(total_weight, 90) self.assertEqual([ student_code, "orange = Fruit('Orange', 30)", "pineapple = Fruit('Pineapple', 60)", "fruits = [orange, pineapple]", "weigh_fruits(fruits)" ], [context.code for context in student.get_context()])
def set_source(code, filename=DEFAULT_STUDENT_FILENAME, sections=False, independent=False, report=MAIN_REPORT): """ Sets the contents of the Source to be the given code. Can also be optionally given a filename. If there is no existing Submission, this contextualizes the Report with a new Submission containing the given code. Otherwise, it creates a Substitution and temporarily replaces the Submission's current main code. Args: code (str): The contents of the source file. filename (str): The filename of the students' code. Defaults to `answer.py`. sections (str or bool): Whether or not the file should be divided into sections. If a str, then it should be a Python regular expression for how the sections are separated. If False, there will be no sections. If True, then the default pattern will be used: '^##### Part (\\d+)$' independent (bool): Whether the separate sections should be considered separate or all existing in an accumulating namespace. report (Report): The report object to store data and feedback in. If left None, defaults to the global MAIN_REPORT. """ if report.submission is None: report.contextualize(Submission({filename: code}, filename, code)) else: backup = Substitution(report.submission.main_code, report.submission.main_file) report[TOOL_NAME]['substitutions'].append(backup) report.submission.replace_main(code, filename) report[TOOL_NAME]['independent'] = independent report[TOOL_NAME]['success'] = True if not sections: report[TOOL_NAME]['sections'] = None report[TOOL_NAME]['section'] = None verify(code, report=report) else: separate_into_sections(report=report)
def load_file_submissions(self, scripts): # Get instructor control scripts all_scripts = [] for script in scripts: script_file_name, script_file_extension = os.path.splitext(script) # Single Python file if script_file_extension in ('.py',): with open(script, 'r') as scripts_file: scripts_contents = scripts_file.read() all_scripts.append((script, scripts_contents)) given_submissions = self.config.submissions # If submission is a directory, use it as a directory adjacent to each ics if os.path.isdir(given_submissions): for script, scripts_contents in all_scripts: directory_pattern = given_submissions submission_dir = normalize_path(directory_pattern, script) submission_files = [ os.path.join(submission_dir, sub) for sub in os.listdir(submission_dir) ] subs = get_python_files(submission_files) for main_file, main_code in subs.items(): new_submission = Submission( main_file=main_file, main_code=main_code, instructor_file=script ) self.submissions.append(Bundle(self.config, scripts_contents, new_submission)) # Otherwise, if the submission is a single file: # Maybe it's a Progsnap DB file? elif given_submissions.endswith('.db'): for script, scripts_contents in all_scripts: self.load_progsnap(given_submissions, instructor_code=scripts_contents) # Otherwise, must just be a single python file. else: main_file = given_submissions load_error, possible_load_error = None, None alternatives = [given_submissions] # if alternative filenames given, we'll queue them up if self.config.alternate_filenames: alternatives.extend(find_possible_filenames(self.config.alternate_filenames)) # Run through all possible filenames for possible in alternatives: try: with open(possible, 'r') as single_submission_file: main_code = single_submission_file.read() main_file = possible break except OSError as e: # Only capture the first possible load error if possible_load_error is None: possible_load_error = e else: # Okay, file does not exist. Load error gets triggered. main_code = None load_error = possible_load_error for script, scripts_contents in all_scripts: new_submission = Submission( main_file=main_file, main_code=main_code, instructor_file=script, load_error=load_error ) self.submissions.append(Bundle(self.config, scripts_contents, new_submission)) return load_error
def __init__(self, code): contextualize_report(Submission({'answer.py': code})) self.student = run()
""" This file is meant to be an idealized example of Pedal with a completely generic autograding environment that wants to customize everything without actually changing anything. """ from pedal.core.submission import Submission from pedal.environments.autograder import setup_pedal from pedal.core.resolver import Resolver autograder = setup_pedal() student_submission = Submission( files={'answer.py': 'print("Hello world!")'}, user={"name": "Ada Lovelace"}, assignment={"name": "#24.3 List Indexing in Functions"}, course={"name": "Introduction to Computer Science"}) contextualize_report(student_submission) from pedal.source import validate validate() resolve()