CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

160 lines
5.0 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. import ast
  17. import logging
  18. import traceback
  19. import collections.abc
  20. LOGGER = logging.getLogger(__name__)
  21. tutor.qtype_inherit("smallbox")
  22. base1, _ = tutor.question("pythoncode")
  23. defaults.update(
  24. {
  25. "csq_soln": "",
  26. "csq_check_function": lambda sub, soln: (
  27. (type(sub) == type(soln)) and (sub == soln)
  28. ),
  29. "csq_input_check": lambda sub: None,
  30. "csq_npoints": 1,
  31. "csq_msg_function": lambda sub, soln: "",
  32. "csq_show_check": False,
  33. "csq_code_pre": "",
  34. "csq_mode": "raw",
  35. "csq_size": 50,
  36. }
  37. )
  38. def gensym(code=""):
  39. pre = n = "___"
  40. count = 0
  41. while n in code:
  42. n = "%s%s" % (pre, count)
  43. count += 1
  44. return n
  45. INVALID_SUBMISSION_MSG = (
  46. '<font color="red">Your submission could not be '
  47. "evaluated. Please check that you have entered a "
  48. "valid Python expression.</font> "
  49. )
  50. def handle_submission(submissions, **info):
  51. sub = submissions[info["csq_name"]].strip()
  52. LOGGER.error("[qtypes.pythonic] submission: %r" % sub)
  53. inp = info["csq_input_check"](sub)
  54. if inp is not None:
  55. return {"score": 0.0, "msg": '<font color="red">%s</font>' % inp}
  56. base1["get_sandbox"](info)
  57. if info["csq_mode"] == "raw":
  58. soln = info["csq_soln"]
  59. else:
  60. code = info["csq_code_pre"]
  61. s = info["csq_soln"]
  62. code += "\n_catsoop_answer = %s" % s
  63. opts = info.get("csq_options", {})
  64. soln = info["sandbox_run_code"](info, code, opts, result_as_string=True)[
  65. "info"
  66. ]["result"]
  67. soln = eval(soln, info)
  68. try:
  69. if sub == "":
  70. LOGGER.debug("[qtypes.pythonic] invalid submission, empty submission")
  71. return {"score": 0.0, "msg": INVALID_SUBMISSION_MSG}
  72. ast.parse(sub, mode="eval")
  73. code = info["csq_code_pre"]
  74. code += "\n_catsoop_answer = %s" % sub
  75. opts = info.get("csq_options", {})
  76. LOGGER.debug("[qtypes.pythonic] code to run:\n%s" % code)
  77. sub = info["sandbox_run_code"](
  78. info, code, opts, result_as_string=info["csq_mode"] != "raw"
  79. )["info"]["result"]
  80. if info["csq_mode"] != "raw":
  81. sub = eval(sub, info)
  82. except Exception as err:
  83. LOGGER.error("[qtypes.pythonic] invalid submission: %r" % sub)
  84. LOGGER.error("[qtypes.pythonic] invalid submission exception=%s" % str(err))
  85. LOGGER.error("[qtypes.pythonic] traceback: %s" % traceback.format_exc())
  86. msg = ""
  87. mfunc = info["csq_msg_function"]
  88. try:
  89. msg += mfunc(sub, soln)
  90. except:
  91. try:
  92. msg += mfunc(sub)
  93. except:
  94. pass
  95. if msg == "":
  96. msg = INVALID_SUBMISSION_MSG
  97. if info["csq_show_check"]:
  98. msg += '<img src="%s" /><br/>' % info["cs_cross_image"]
  99. return {"score": 0.0, "msg": msg}
  100. check = info["csq_check_function"]
  101. try:
  102. check_result = check(sub, soln)
  103. except:
  104. err = info["csm_errors"]
  105. e = err.html_format(err.clear_info(info, traceback.format_exc()))
  106. check_result = (
  107. 0.0,
  108. '<font color="red">An error occurred in the checker: <pre>%s</pre></font>'
  109. % e,
  110. )
  111. if isinstance(check_result, collections.abc.Mapping):
  112. score = check_result["score"]
  113. msg = check_result["msg"]
  114. elif isinstance(check_result, collections.abc.Sequence):
  115. score, msg = check_result
  116. else:
  117. score = check_result
  118. mfunc = info["csq_msg_function"]
  119. try:
  120. msg = mfunc(sub, soln)
  121. except:
  122. try:
  123. msg = mfunc(sub)
  124. except:
  125. msg = ""
  126. percent = float(score)
  127. response = ""
  128. if info["csq_show_check"]:
  129. if percent == 1.0:
  130. response = '<img src="%s" /><br/>' % info["cs_check_image"]
  131. elif percent == 0.0:
  132. response = '<img src="%s" /><br/>' % info["cs_cross_image"]
  133. response += msg
  134. return {"score": percent, "msg": response}
  135. def answer_display(**info):
  136. if info["csq_mode"] == "raw":
  137. out = "<p>Solution: <tt>%r</tt><p>" % (info["csq_soln"],)
  138. else:
  139. out = "<p>Solution: <tt>%s</tt><p>" % (info["csq_soln"],)
  140. return out