From 87d8a56a93c397568764f8d2aee5ecfcdc518ebf Mon Sep 17 00:00:00 2001 From: Will Daly Date: Mon, 1 Apr 2013 13:27:31 -0400 Subject: [PATCH] CustomResponse now imports chem packages into the global context for scripts to access. This was removed accidentally during an earlier refactoring, which caused customresponse to raise an exception for chem problems. Unit tests verify the changes --- common/lib/capa/capa/capa_problem.py | 5 ++ common/lib/capa/capa/responsetypes.py | 70 ++++++++++++------- .../lib/capa/capa/tests/test_responsetypes.py | 60 ++++++++++++++++ 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 696b12377f..6580114bcc 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -32,6 +32,8 @@ from copy import deepcopy import chem import chem.miller +import chem.chemcalc +import chem.chemtools import verifiers import verifiers.draganddrop @@ -67,6 +69,9 @@ global_context = {'random': random, 'scipy': scipy, 'calc': calc, 'eia': eia, + 'chemcalc': chem.chemcalc, + 'chemtools': chem.chemtools, + 'miller': chem.miller, 'draganddrop': verifiers.draganddrop} # These should be removed from HTML output, including all subelements diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 5b1b46d858..ec1d5ffe1b 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2,7 +2,8 @@ # File: courseware/capa/responsetypes.py # ''' -Problem response evaluation. Handles checking of student responses, of a variety of types. +Problem response evaluation. Handles checking of +student responses, of a variety of types. Used by capa_problem.py ''' @@ -10,7 +11,6 @@ Used by capa_problem.py # standard library imports import abc import cgi -import hashlib import inspect import json import logging @@ -34,7 +34,10 @@ from .correctmap import CorrectMap from datetime import datetime from .util import * from lxml import etree -from lxml.html.soupparser import fromstring as fromstring_bs # uses Beautiful Soup!!! FIXME? + +# uses Beautiful Soup!!! FIXME? +from lxml.html.soupparser import fromstring as fromstring_bs + import xqueue_interface log = logging.getLogger(__name__) @@ -243,13 +246,17 @@ class LoncapaResponse(object): # hint specified by function? hintfn = hintgroup.get('hintfn') if hintfn: - # Hint is determined by a function defined in the stanza instead cfn = xml.get('cfn') @@ -973,8 +986,8 @@ def sympy_check2(): else: msg = "%s: can't find cfn %s in context" % ( unicode(self), cfn) - msg += "\nSee XML source line %s" % getattr(self.xml, 'sourceline', - '') + msg += ("\nSee XML source line %s" % + getattr(self.xml, 'sourceline', '')) raise LoncapaProblemError(msg) if not self.code: @@ -1010,14 +1023,17 @@ def sympy_check2(): log.error(msg) raise Exception(msg) - # global variable in context which holds the Presentation MathML from dynamic math input + # global variable in context which holds the + # Presentation MathML from dynamic math input # ordered list of dynamath responses dynamath = [student_answers.get(k + '_dynamath', None) for k in idset] # if there is only one box, and it's empty, then don't evaluate if len(idset) == 1 and not submission[0]: - # default to no error message on empty answer (to be consistent with other - # responsetypes) but allow author to still have the old behavior by setting + # default to no error message on empty answer + # (to be consistent with other + # responsetypes) but allow author to still + # have the old behavior by setting # empty_answer_err attribute msg = ('No answer entered!' if self.xml.get('empty_answer_err') else '') @@ -1092,7 +1108,8 @@ def sympy_check2(): try: answer_given = submission[0] if ( len(idset) == 1) else submission - # handle variable number of arguments in check function, for backwards compatibility + # handle variable number of arguments in check function, + # for backwards compatibility # with various Tutor2 check functions args = [self.expect, answer_given, student_answers, self.answer_ids[0]] @@ -1124,7 +1141,8 @@ def sympy_check2(): msg = ret.get('msg', None) msg = self.clean_message_html(msg) - # If there is only one input, apply the message to that input + # If there is only one input, apply the message to + # that input # Otherwise, apply the message to the whole problem if len(idset) > 1: overall_message = msg @@ -1137,7 +1155,8 @@ def sympy_check2(): # 'input_list': [{ 'ok': BOOLEAN, 'msg': STRING }, ...] } # # This allows the function to return an 'overall message' - # that applies to the entire problem, as well as correct/incorrect + # that applies to the entire problem, as well as + # correct/incorrect # status and messages for individual inputs elif 'input_list' in ret: overall_message = ret.get('overall_message', '') @@ -1370,7 +1389,8 @@ class CodeResponse(LoncapaResponse): tests = self.xml.get('tests') - # Extract 'answer' and 'initial_display' from XML. Note that the code to be exec'ed here is: + # Extract 'answer' and 'initial_display' from XML. + # Note that the code to be exec'ed here is: # (1) Internal edX code, i.e. NOT student submissions, and # (2) The code should only define the strings 'initial_display', 'answer', # 'preamble', 'test_program' @@ -1391,7 +1411,8 @@ class CodeResponse(LoncapaResponse): " 'answer' and/or 'initial_display' in ..." % err) raise Exception(err) - # Finally, make the ExternalResponse input XML format conform to the generic + # Finally, make the ExternalResponse input XML format + # conform to the generic # exteral grader interface # The XML tagging of grader_payload is pyxserver-specific grader_payload = '' @@ -1500,7 +1521,8 @@ class CodeResponse(LoncapaResponse): # TODO: Find out how this is used elsewhere, if any self.context['correct'] = correctness - # Replace 'oldcmap' with new grading results if queuekey matches. If queuekey + # Replace 'oldcmap' with new grading results if queuekey matches. + # If queuekey # does not match, we keep waiting for the score_msg whose key actually # matches if oldcmap.is_right_queuekey(self.answer_id, queuekey): diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 9ade578622..bf64d3cc69 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -905,6 +905,66 @@ class CustomResponseTest(ResponseTest): problem.grade_answers({'1_2_1': '42'}) + def test_module_imports_inline(self): + ''' + Check that the correct modules are available to custom + response scripts + ''' + + for module_name in ['random', 'numpy', 'math', 'scipy', + 'calc', 'eia', 'chemcalc', 'chemtools', + 'miller', 'draganddrop']: + + # Create a script that checks that the name is defined + # If the name is not defined, then the script + # will raise an exception + script = textwrap.dedent(''' + correct[0] = 'correct' + assert('%s' in globals())''' % module_name) + + # Create the problem + problem = self.build_problem(answer=script) + + # Expect that we can grade an answer without + # getting an exception + try: + problem.grade_answers({'1_2_1': '42'}) + + except ResponseError: + self.fail("Could not use name '%s' in custom response" + % module_name) + + def test_module_imports_function(self): + ''' + Check that the correct modules are available to custom + response scripts + ''' + + for module_name in ['random', 'numpy', 'math', 'scipy', + 'calc', 'eia', 'chemcalc', 'chemtools', + 'miller', 'draganddrop']: + + # Create a script that checks that the name is defined + # If the name is not defined, then the script + # will raise an exception + script = textwrap.dedent(''' + def check_func(expect, answer_given): + assert('%s' in globals()) + return True''' % module_name) + + # Create the problem + problem = self.build_problem(script=script, cfn="check_func") + + # Expect that we can grade an answer without + # getting an exception + try: + problem.grade_answers({'1_2_1': '42'}) + + except ResponseError: + self.fail("Could not use name '%s' in custom response" + % module_name) + + class SchematicResponseTest(ResponseTest): from response_xml_factory import SchematicResponseXMLFactory xml_factory_class = SchematicResponseXMLFactory