Cleaning up pep8 issues, including extraneous imports
This commit is contained in:
@@ -12,7 +12,6 @@ import logging
|
||||
import math
|
||||
import numpy
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import re
|
||||
import scipy
|
||||
@@ -20,58 +19,58 @@ import struct
|
||||
|
||||
from lxml import etree
|
||||
from lxml.etree import Element
|
||||
from xml.sax.saxutils import escape, unescape
|
||||
from xml.sax.saxutils import unescape
|
||||
|
||||
from util import contextualize_text
|
||||
import inputtypes
|
||||
|
||||
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, StudentInputError, TrueFalseResponse, ExternalResponse,ImageResponse,OptionResponse, SymbolicResponse
|
||||
from responsetypes import NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse
|
||||
|
||||
import calc
|
||||
import eia
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
response_types = {'numericalresponse':NumericalResponse,
|
||||
'formularesponse':FormulaResponse,
|
||||
'customresponse':CustomResponse,
|
||||
'schematicresponse':SchematicResponse,
|
||||
'externalresponse':ExternalResponse,
|
||||
'multiplechoiceresponse':MultipleChoiceResponse,
|
||||
'truefalseresponse':TrueFalseResponse,
|
||||
'imageresponse':ImageResponse,
|
||||
'optionresponse':OptionResponse,
|
||||
'symbolicresponse':SymbolicResponse,
|
||||
response_types = {'numericalresponse': NumericalResponse,
|
||||
'formularesponse': FormulaResponse,
|
||||
'customresponse': CustomResponse,
|
||||
'schematicresponse': SchematicResponse,
|
||||
'externalresponse': ExternalResponse,
|
||||
'multiplechoiceresponse': MultipleChoiceResponse,
|
||||
'truefalseresponse': TrueFalseResponse,
|
||||
'imageresponse': ImageResponse,
|
||||
'optionresponse': OptionResponse,
|
||||
'symbolicresponse': SymbolicResponse,
|
||||
}
|
||||
entry_types = ['textline', 'schematic', 'choicegroup','textbox','imageinput','optioninput']
|
||||
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
|
||||
response_properties = ["responseparam", "answer"] # these get captured as student responses
|
||||
entry_types = ['textline', 'schematic', 'choicegroup', 'textbox', 'imageinput', 'optioninput']
|
||||
solution_types = ['solution'] # extra things displayed after "show answers" is pressed
|
||||
response_properties = ["responseparam", "answer"] # these get captured as student responses
|
||||
|
||||
# How to convert from original XML to HTML
|
||||
# We should do this with xlst later
|
||||
html_transforms = {'problem': {'tag':'div'},
|
||||
"numericalresponse": {'tag':'span'},
|
||||
"customresponse": {'tag':'span'},
|
||||
"externalresponse": {'tag':'span'},
|
||||
"schematicresponse": {'tag':'span'},
|
||||
"formularesponse": {'tag':'span'},
|
||||
"symbolicresponse": {'tag':'span'},
|
||||
"multiplechoiceresponse": {'tag':'span'},
|
||||
"text": {'tag':'span'},
|
||||
"math": {'tag':'span'},
|
||||
html_transforms = {'problem': {'tag': 'div'},
|
||||
"numericalresponse": {'tag': 'span'},
|
||||
"customresponse": {'tag': 'span'},
|
||||
"externalresponse": {'tag': 'span'},
|
||||
"schematicresponse": {'tag': 'span'},
|
||||
"formularesponse": {'tag': 'span'},
|
||||
"symbolicresponse": {'tag': 'span'},
|
||||
"multiplechoiceresponse": {'tag': 'span'},
|
||||
"text": {'tag': 'span'},
|
||||
"math": {'tag': 'span'},
|
||||
}
|
||||
|
||||
global_context={'random':random,
|
||||
'numpy':numpy,
|
||||
'math':math,
|
||||
'scipy':scipy,
|
||||
'calc':calc,
|
||||
'eia':eia}
|
||||
global_context = {'random': random,
|
||||
'numpy': numpy,
|
||||
'math': math,
|
||||
'scipy': scipy,
|
||||
'calc': calc,
|
||||
'eia': eia}
|
||||
|
||||
# These should be removed from HTML output, including all subelements
|
||||
html_problem_semantics = ["responseparam", "answer", "script"]
|
||||
# These should be removed from HTML output, but keeping subelements
|
||||
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text","externalresponse",'symbolicresponse']
|
||||
html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formularesponse", "text", "externalresponse", 'symbolicresponse']
|
||||
|
||||
# removed in MC
|
||||
## These should be transformed
|
||||
@@ -82,6 +81,7 @@ html_skip = ["numericalresponse", "customresponse", "schematicresponse", "formul
|
||||
# "solution":inputtypes.solution.render,
|
||||
# }
|
||||
|
||||
|
||||
class LoncapaProblem(object):
|
||||
def __init__(self, fileobject, id, state=None, seed=None, system=None):
|
||||
## Initialize class variables from state
|
||||
@@ -107,22 +107,22 @@ class LoncapaProblem(object):
|
||||
|
||||
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
|
||||
if not self.seed:
|
||||
self.seed=struct.unpack('i', os.urandom(4))[0]
|
||||
self.seed = struct.unpack('i', os.urandom(4))[0]
|
||||
|
||||
## Parse XML file
|
||||
if getattr(system,'DEBUG',False):
|
||||
if getattr(system, 'DEBUG', False):
|
||||
log.info("[courseware.capa.capa_problem.lcp.init] fileobject = %s" % fileobject)
|
||||
file_text = fileobject.read()
|
||||
self.fileobject = fileobject # save it, so we can use for debugging information later
|
||||
self.fileobject = fileobject # save it, so we can use for debugging information later
|
||||
# Convert startouttext and endouttext to proper <text></text>
|
||||
# TODO: Do with XML operations
|
||||
file_text = re.sub("startouttext\s*/","text",file_text)
|
||||
file_text = re.sub("endouttext\s*/","/text",file_text)
|
||||
file_text = re.sub("startouttext\s*/", "text", file_text)
|
||||
file_text = re.sub("endouttext\s*/", "/text", file_text)
|
||||
self.tree = etree.XML(file_text)
|
||||
|
||||
self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map = self.student_answers)
|
||||
self.preprocess_problem(self.tree, correct_map=self.correct_map, answer_map=self.student_answers)
|
||||
self.context = self.extract_context(self.tree, seed=self.seed)
|
||||
for response in self.tree.xpath('//'+"|//".join(response_types)):
|
||||
for response in self.tree.xpath('//' + "|//".join(response_types)):
|
||||
responder = response_types[response.tag](response, self.context, self.system)
|
||||
responder.preprocess_response()
|
||||
|
||||
@@ -130,34 +130,34 @@ class LoncapaProblem(object):
|
||||
return u"LoncapaProblem ({0})".format(self.fileobject)
|
||||
|
||||
def get_state(self):
|
||||
''' Stored per-user session data neeeded to:
|
||||
''' Stored per-user session data neeeded to:
|
||||
1) Recreate the problem
|
||||
2) Populate any student answers. '''
|
||||
return {'seed':self.seed,
|
||||
'student_answers':self.student_answers,
|
||||
'correct_map':self.correct_map,
|
||||
'done':self.done}
|
||||
return {'seed': self.seed,
|
||||
'student_answers': self.student_answers,
|
||||
'correct_map': self.correct_map,
|
||||
'done': self.done}
|
||||
|
||||
def get_max_score(self):
|
||||
'''
|
||||
TODO: multiple points for programming problems.
|
||||
'''
|
||||
sum = 0
|
||||
for et in entry_types:
|
||||
sum = sum + self.tree.xpath('count(//'+et+')')
|
||||
sum = 0
|
||||
for et in entry_types:
|
||||
sum = sum + self.tree.xpath('count(//' + et + ')')
|
||||
return int(sum)
|
||||
|
||||
def get_score(self):
|
||||
correct=0
|
||||
correct = 0
|
||||
for key in self.correct_map:
|
||||
if self.correct_map[key] == u'correct':
|
||||
correct += 1
|
||||
if (not self.student_answers) or len(self.student_answers)==0:
|
||||
return {'score':0,
|
||||
'total':self.get_max_score()}
|
||||
if (not self.student_answers) or len(self.student_answers) == 0:
|
||||
return {'score': 0,
|
||||
'total': self.get_max_score()}
|
||||
else:
|
||||
return {'score':correct,
|
||||
'total':self.get_max_score()}
|
||||
return {'score': correct,
|
||||
'total': self.get_max_score()}
|
||||
|
||||
def grade_answers(self, answers):
|
||||
'''
|
||||
@@ -168,38 +168,36 @@ class LoncapaProblem(object):
|
||||
Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123
|
||||
'''
|
||||
self.student_answers = answers
|
||||
context=self.extract_context(self.tree)
|
||||
self.correct_map = dict()
|
||||
problems_simple = self.extract_problems(self.tree)
|
||||
for response in problems_simple:
|
||||
grader = response_types[response.tag](response, self.context, self.system)
|
||||
results = grader.get_score(answers) # call the responsetype instance to do the actual grading
|
||||
results = grader.get_score(answers) # call the responsetype instance to do the actual grading
|
||||
self.correct_map.update(results)
|
||||
return self.correct_map
|
||||
|
||||
def get_question_answers(self):
|
||||
"""Returns a dict of answer_ids to answer values. If we can't generate
|
||||
an answer (this sometimes happens in customresponses), that answer_id is
|
||||
not included. Called by "show answers" button JSON request
|
||||
an answer (this sometimes happens in customresponses), that answer_id is
|
||||
not included. Called by "show answers" button JSON request
|
||||
(see capa_module)
|
||||
"""
|
||||
context=self.extract_context(self.tree)
|
||||
answer_map = dict()
|
||||
problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries
|
||||
problems_simple = self.extract_problems(self.tree) # purified (flat) XML tree of just response queries
|
||||
for response in problems_simple:
|
||||
responder = response_types[response.tag](response, self.context, self.system) # instance of numericalresponse, customresponse,...
|
||||
responder = response_types[response.tag](response, self.context, self.system) # instance of numericalresponse, customresponse,...
|
||||
results = responder.get_answers()
|
||||
answer_map.update(results) # dict of (id,correct_answer)
|
||||
answer_map.update(results) # dict of (id,correct_answer)
|
||||
|
||||
# example for the following: <textline size="5" correct_answer="saturated" />
|
||||
for entry in problems_simple.xpath("//"+"|//".join(response_properties+entry_types)):
|
||||
answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline
|
||||
for entry in problems_simple.xpath("//" + "|//".join(response_properties + entry_types)):
|
||||
answer = entry.get('correct_answer') # correct answer, when specified elsewhere, eg in a textline
|
||||
if answer:
|
||||
answer_map[entry.get('id')] = contextualize_text(answer, self.context)
|
||||
|
||||
# include solutions from <solution>...</solution> stanzas
|
||||
# Tentative merge; we should figure out how we want to handle hints and solutions
|
||||
for entry in self.tree.xpath("//"+"|//".join(solution_types)):
|
||||
for entry in self.tree.xpath("//" + "|//".join(solution_types)):
|
||||
answer = etree.tostring(entry)
|
||||
if answer:
|
||||
answer_map[entry.get('id')] = answer
|
||||
@@ -207,11 +205,10 @@ class LoncapaProblem(object):
|
||||
return answer_map
|
||||
|
||||
def get_answer_ids(self):
|
||||
"""Return the IDs of all the responses -- these are the keys used for
|
||||
the dicts returned by grade_answers and get_question_answers. (Though
|
||||
"""Return the IDs of all the responses -- these are the keys used for
|
||||
the dicts returned by grade_answers and get_question_answers. (Though
|
||||
get_question_answers may only return a subset of these."""
|
||||
answer_ids = []
|
||||
context=self.extract_context(self.tree)
|
||||
problems_simple = self.extract_problems(self.tree)
|
||||
for response in problems_simple:
|
||||
responder = response_types[response.tag](response, self.context)
|
||||
@@ -223,35 +220,35 @@ class LoncapaProblem(object):
|
||||
|
||||
return answer_ids
|
||||
|
||||
|
||||
# ======= Private ========
|
||||
|
||||
def extract_context(self, tree, seed = struct.unpack('i', os.urandom(4))[0]): # private
|
||||
def extract_context(self, tree, seed=struct.unpack('i', os.urandom(4))[0]): # private
|
||||
'''
|
||||
Extract content of <script>...</script> from the problem.xml file, and exec it in the
|
||||
context of this problem. Provides ability to randomize problems, and also set
|
||||
variables for problem answer checking.
|
||||
|
||||
|
||||
Problem XML goes to Python execution context. Runs everything in script tags
|
||||
'''
|
||||
random.seed(self.seed)
|
||||
context = {'global_context':global_context} # save global context in here also
|
||||
context.update(global_context) # initialize context to have stuff in global_context
|
||||
context['__builtins__'] = globals()['__builtins__'] # put globals there also
|
||||
context['the_lcp'] = self # pass instance of LoncapaProblem in
|
||||
context = {'global_context': global_context} # save global context in here also
|
||||
context.update(global_context) # initialize context to have stuff in global_context
|
||||
context['__builtins__'] = globals()['__builtins__'] # put globals there also
|
||||
context['the_lcp'] = self # pass instance of LoncapaProblem in
|
||||
|
||||
#for script in tree.xpath('/problem/script'):
|
||||
for script in tree.findall('.//script'):
|
||||
stype = script.get('type')
|
||||
if stype:
|
||||
if 'javascript' in stype: continue # skip javascript
|
||||
if 'perl' in stype: continue # skip perl
|
||||
# TODO: evaluate only python
|
||||
if 'javascript' in stype:
|
||||
continue # skip javascript
|
||||
if 'perl' in stype:
|
||||
continue # skip perl
|
||||
# TODO: evaluate only python
|
||||
code = script.text
|
||||
XMLESC = {"'": "'", """: '"'}
|
||||
code = unescape(code,XMLESC)
|
||||
code = unescape(code, XMLESC)
|
||||
try:
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
exec code in context, context # use "context" for global context; thus defs in code are global within code
|
||||
except Exception:
|
||||
log.exception("Error while execing code: " + code)
|
||||
return context
|
||||
@@ -265,11 +262,11 @@ class LoncapaProblem(object):
|
||||
if problemtree.tag in html_problem_semantics:
|
||||
return
|
||||
|
||||
problemid = problemtree.get('id') # my ID
|
||||
|
||||
problemid = problemtree.get('id') # my ID
|
||||
|
||||
# used to be
|
||||
# if problemtree.tag in html_special_response:
|
||||
|
||||
|
||||
if problemtree.tag in inputtypes.get_input_xml_tags():
|
||||
# status is currently the answer for the problem ID for the input element,
|
||||
# but it will turn into a dict containing both the answer and any associated message
|
||||
@@ -283,7 +280,7 @@ class LoncapaProblem(object):
|
||||
value = self.student_answers[problemid]
|
||||
|
||||
#### This code is a hack. It was merged to help bring two branches
|
||||
#### in sync, but should be replaced. msg should be passed in a
|
||||
#### in sync, but should be replaced. msg should be passed in a
|
||||
#### response_type
|
||||
# prepare the response message, if it exists in correct_map
|
||||
if 'msg' in self.correct_map:
|
||||
@@ -296,112 +293,111 @@ class LoncapaProblem(object):
|
||||
# do the rendering
|
||||
# This should be broken out into a helper function
|
||||
# that handles all input objects
|
||||
render_object = inputtypes.SimpleInput(system = self.system,
|
||||
xml = problemtree,
|
||||
state = {'value':value,
|
||||
'status': status,
|
||||
'id':problemtree.get('id'),
|
||||
'feedback':{'message':msg}
|
||||
},
|
||||
use = 'capa_input')
|
||||
return render_object.get_html() #function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
|
||||
render_object = inputtypes.SimpleInput(system=self.system,
|
||||
xml=problemtree,
|
||||
state={'value': value,
|
||||
'status': status,
|
||||
'id': problemtree.get('id'),
|
||||
'feedback': {'message': msg}
|
||||
},
|
||||
use='capa_input')
|
||||
return render_object.get_html() # function(problemtree, value, status, msg) # render the special response (textline, schematic,...)
|
||||
|
||||
tree=Element(problemtree.tag)
|
||||
tree = Element(problemtree.tag)
|
||||
for item in problemtree:
|
||||
subitems = self.extract_html(item)
|
||||
if subitems is not None:
|
||||
for subitem in subitems:
|
||||
tree.append(subitem)
|
||||
for (key,value) in problemtree.items():
|
||||
for (key, value) in problemtree.items():
|
||||
tree.set(key, value)
|
||||
|
||||
tree.text=problemtree.text
|
||||
tree.tail=problemtree.tail
|
||||
tree.text = problemtree.text
|
||||
tree.tail = problemtree.tail
|
||||
|
||||
if problemtree.tag in html_transforms:
|
||||
tree.tag=html_transforms[problemtree.tag]['tag']
|
||||
tree.tag = html_transforms[problemtree.tag]['tag']
|
||||
# Reset attributes. Otherwise, we get metadata in HTML
|
||||
# (e.g. answers)
|
||||
# (e.g. answers)
|
||||
# TODO: We should remove and not zero them.
|
||||
# I'm not sure how to do that quickly with lxml
|
||||
for k in tree.keys():
|
||||
tree.set(k,"")
|
||||
tree.set(k, "")
|
||||
|
||||
# TODO: Fix. This loses Element().tail
|
||||
#if problemtree.tag in html_skip:
|
||||
# return tree
|
||||
return [tree]
|
||||
|
||||
def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()): # private
|
||||
def preprocess_problem(self, tree, correct_map=dict(), answer_map=dict()): # private
|
||||
'''
|
||||
Assign IDs to all the responses
|
||||
Assign IDs to all the responses
|
||||
Assign sub-IDs to all entries (textline, schematic, etc.)
|
||||
Annoted correctness and value
|
||||
In-place transformation
|
||||
'''
|
||||
response_id = 1
|
||||
for response in tree.xpath('//'+"|//".join(response_types)):
|
||||
response_id_str=self.problem_id+"_"+str(response_id)
|
||||
response.attrib['id']=response_id_str
|
||||
for response in tree.xpath('//' + "|//".join(response_types)):
|
||||
response_id_str = self.problem_id + "_" + str(response_id)
|
||||
response.attrib['id'] = response_id_str
|
||||
if response_id not in correct_map:
|
||||
correct = 'unsubmitted'
|
||||
response.attrib['state'] = correct
|
||||
response_id = response_id + 1
|
||||
answer_id = 1
|
||||
for entry in tree.xpath("|".join(['//'+response.tag+'[@id=$id]//'+x for x in (entry_types + solution_types)]),
|
||||
for entry in tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x for x in (entry_types + solution_types)]),
|
||||
id=response_id_str):
|
||||
# assign one answer_id for each entry_type or solution_type
|
||||
# assign one answer_id for each entry_type or solution_type
|
||||
entry.attrib['response_id'] = str(response_id)
|
||||
entry.attrib['answer_id'] = str(answer_id)
|
||||
entry.attrib['id'] = "%s_%i_%i"%(self.problem_id, response_id, answer_id)
|
||||
answer_id=answer_id+1
|
||||
entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
|
||||
answer_id = answer_id + 1
|
||||
|
||||
# <solution>...</solution> may not be associated with any specific response; give IDs for those separately
|
||||
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
|
||||
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
|
||||
solution_id = 1
|
||||
for solution in tree.findall('.//solution'):
|
||||
solution.attrib['id'] = "%s_solution_%i"%(self.problem_id, solution_id)
|
||||
solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id)
|
||||
solution_id += 1
|
||||
|
||||
def extract_problems(self, problem_tree):
|
||||
''' Remove layout from the problem, and give a purified XML tree of just the problems '''
|
||||
problem_tree=copy.deepcopy(problem_tree)
|
||||
tree=Element('problem')
|
||||
for response in problem_tree.xpath("//"+"|//".join(response_types)):
|
||||
problem_tree = copy.deepcopy(problem_tree)
|
||||
tree = Element('problem')
|
||||
for response in problem_tree.xpath("//" + "|//".join(response_types)):
|
||||
newresponse = copy.copy(response)
|
||||
for e in newresponse:
|
||||
for e in newresponse:
|
||||
newresponse.remove(e)
|
||||
# copy.copy is needed to make xpath work right. Otherwise, it starts at the root
|
||||
# of the tree. We should figure out if there's some work-around
|
||||
for e in copy.copy(response).xpath("//"+"|//".join(response_properties+entry_types)):
|
||||
for e in copy.copy(response).xpath("//" + "|//".join(response_properties + entry_types)):
|
||||
newresponse.append(e)
|
||||
|
||||
|
||||
tree.append(newresponse)
|
||||
return tree
|
||||
|
||||
if __name__=='__main__':
|
||||
problem_id='simpleFormula'
|
||||
if __name__ == '__main__':
|
||||
problem_id = 'simpleFormula'
|
||||
filename = 'simpleFormula.xml'
|
||||
|
||||
problem_id='resistor'
|
||||
problem_id = 'resistor'
|
||||
filename = 'resistor.xml'
|
||||
|
||||
|
||||
lcp = LoncapaProblem(filename, problem_id)
|
||||
|
||||
|
||||
context = lcp.extract_context(lcp.tree)
|
||||
problem = lcp.extract_problems(lcp.tree)
|
||||
print lcp.grade_problems({'resistor_2_1':'1.0','resistor_3_1':'2.0'})
|
||||
print lcp.grade_problems({'resistor_2_1': '1.0', 'resistor_3_1': '2.0'})
|
||||
#print lcp.grade_problems({'simpleFormula_2_1':'3*x^3'})
|
||||
#numericalresponse(problem, context)
|
||||
|
||||
|
||||
#print etree.tostring((lcp.tree))
|
||||
print '============'
|
||||
print
|
||||
#print etree.tostring(lcp.extract_problems(lcp.tree))
|
||||
print lcp.get_html()
|
||||
#print extract_context(tree)
|
||||
|
||||
|
||||
|
||||
|
||||
# def handle_fr(self, element):
|
||||
@@ -411,4 +407,4 @@ if __name__=='__main__':
|
||||
# "sample_range":dict(zip(variables, sranges)),
|
||||
# "samples_count": numsamples,
|
||||
# "id":id,
|
||||
# self.questions[self.lid]=problem
|
||||
# self.questions[self.lid]=problem
|
||||
|
||||
Reference in New Issue
Block a user