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