changeset 1592:dc3bcbe9da8c

fix for pythoncode set() issue
author adam j hartz <hz@mit.edu>
date Sat, 08 Jun 2019 23:08:01 -0400
parents 5d02a37a5b85
children 8d3f61d9823f
files CHANGELOG catsoop/__QTYPES__/pythoncode/__SANDBOXES__/bwrap.py catsoop/__QTYPES__/pythoncode/__SANDBOXES__/python.py catsoop/base_context.py catsoop/util.py
diffstat 5 files changed, 107 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG	Wed Jun 05 14:19:48 2019 -0400
+++ b/CHANGELOG	Sat Jun 08 23:08:01 2019 -0400
@@ -69,6 +69,9 @@
 
 FIXED:
 
+  * Fixed an issue whereby "set()" could not be accepted as an answer to a
+    `pythonic` or `pythonliteral` question.
+
   * Fix for malformed HTML in `multiexpression` question output.
 
   * Fix for "preview" in `richtext` question type.
--- a/catsoop/__QTYPES__/pythoncode/__SANDBOXES__/bwrap.py	Wed Jun 05 14:19:48 2019 -0400
+++ b/catsoop/__QTYPES__/pythoncode/__SANDBOXES__/bwrap.py	Sat Jun 08 23:08:01 2019 -0400
@@ -15,7 +15,6 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import ast
 import sys
 import time
 import uuid
@@ -138,7 +137,7 @@
     if len(n) == 2:  # should be this
         out, log = n
         try:
-            log = ast.literal_eval(log)
+            log = context['csm_util'].literal_eval(log)
         except:
             log = {}
 
--- a/catsoop/__QTYPES__/pythoncode/__SANDBOXES__/python.py	Wed Jun 05 14:19:48 2019 -0400
+++ b/catsoop/__QTYPES__/pythoncode/__SANDBOXES__/python.py	Sat Jun 08 23:08:01 2019 -0400
@@ -139,7 +139,7 @@
     if len(n) == 2:  # should be this
         out, log = n
         try:
-            log = ast.literal_eval(log.strip())
+            log = context['csm_util'].literal_eval(log)
         except:
             log = {}
 
--- a/catsoop/base_context.py	Wed Jun 05 14:19:48 2019 -0400
+++ b/catsoop/base_context.py	Sat Jun 08 23:08:01 2019 -0400
@@ -407,6 +407,7 @@
     "thirdparty",
     "tutor",
     "user",
+    "util",
 ]
 
 cs_all_thirdparty = ["data_uri"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/catsoop/util.py	Sat Jun 08 23:08:01 2019 -0400
@@ -0,0 +1,101 @@
+# This file is part of CAT-SOOP
+# Copyright (c) 2011-2019 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Extra utilities that don't have a home anywhere else
+"""
+
+import ast
+
+from collections import OrderedDict
+from datetime import datetime, timedelta
+
+_literal_eval_funcs = {
+    "OrderedDict": OrderedDict,
+    "frozenset": frozenset,
+    "set": set,
+    "dict": dict,
+    "list": list,
+    "tuple": tuple,
+    "datetime": datetime,
+    "timedelta": timedelta,
+}
+
+
+def literal_eval(node_or_string):
+    """
+    Helper function to read a log entry and return the associated Python
+    object.  Forked from Python 3.5's ast.literal_eval function:
+
+    Safely evaluate an expression node or a string containing a Python
+    expression.  The string or node provided may only consist of the following
+    Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
+    sets, booleans, and None.
+
+    Modified for CAT-SOOP to include collections.OrderedDict.
+    """
+    if isinstance(node_or_string, str):
+        node_or_string = ast.parse(node_or_string, mode="eval")
+    if isinstance(node_or_string, ast.Expression):
+        node_or_string = node_or_string.body
+
+    def _convert(node):
+        if isinstance(node, (ast.Str, ast.Bytes)):
+            return node.s
+        elif isinstance(node, ast.Num):
+            return node.n
+        elif isinstance(node, ast.Tuple):
+            return tuple(map(_convert, node.elts))
+        elif isinstance(node, ast.List):
+            return list(map(_convert, node.elts))
+        elif isinstance(node, ast.Set):
+            return set(map(_convert, node.elts))
+        elif isinstance(node, ast.Dict):
+            return dict(
+                (_convert(k), _convert(v)) for k, v in zip(node.keys, node.values)
+            )
+        elif isinstance(node, ast.NameConstant):
+            return node.value
+        elif (
+            isinstance(node, ast.UnaryOp)
+            and isinstance(node.op, (ast.UAdd, ast.USub))
+            and isinstance(node.operand, (ast.Num, ast.UnaryOp, ast.BinOp))
+        ):
+            operand = _convert(node.operand)
+            if isinstance(node.op, ast.UAdd):
+                return +operand
+            else:
+                return -operand
+        elif (
+            isinstance(node, ast.BinOp)
+            and isinstance(node.op, (ast.Add, ast.Sub))
+            and isinstance(node.right, (ast.Num, ast.UnaryOp, ast.BinOp))
+            and isinstance(node.left, (ast.Num, ast.UnaryOp, ast.BinOp))
+        ):
+            left = _convert(node.left)
+            right = _convert(node.right)
+            if isinstance(node.op, ast.Add):
+                return left + right
+            else:
+                return left - right
+        elif (
+            isinstance(node, ast.Call)
+            and isinstance(node.func, ast.Name)
+            and node.func.id in _literal_eval_funcs
+        ):
+            return _literal_eval_funcs[node.func.id](*(_convert(i) for i in node.args))
+        raise ValueError("malformed node or string: " + repr(node))
+
+    return _convert(node_or_string)