Source code for pysys.utils.safeeval

#!/usr/bin/env python
# PySys System Test Framework, Copyright (C) 2006-2022 M.B.Grieve

# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

# This library 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
# Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


# This set of imports was copied from BaseTest (minus a little tidying up, e.g. removing BaseTest-specific dependencies) 
# to preserve compatibility with 1.5.0 PySys tests using assertEval, before we started controlling the environment used 
# for eval more carefully. 

# It still contains lots of symbols which are likely to be useful when performing an eval such as IS_WINDOWS, etc

"""
Contains the `safeEval` function.
"""

import os.path, time, threading, logging, sys
import re
import math
import json

from pysys import log
from pysys.config.project import Project
from pysys.constants import *
from pysys.exceptions import *

from pysys.utils.pycompat import *
from pysys.utils.fileutils import pathexists
import pysys

import importlib
from importlib import import_module

class SafeEvalException(Exception): pass

[docs]def safeEval(expr, errorMessage='Failed to evaluate "{expr}" due to {error}', emptyNamespace=False, extraNamespace={}): """ Executes eval(...) on the specified string expression, using a controlled globals()/locals() environment to ensure we do not break compatibility between PySys versions, and that a sensible set of PySys constants and modules are available. Unless ``emptyNamespace=True``, the global environment used for evaluation includes the ``os.path``, ``math``, ``sys``, ``re``, ``json``, and ``locale`` standard Python modules, as well as the ``pysys`` module and the contents of the `pysys.constants` module, e.g. ``IS_WINDOWS``. If necessary, symbols for additional modules can be imported dynamically using ``import_module``:: x = safeEval("import_module('difflib').get_close_matches('app', ['apple', 'orange', 'applic']") If an error occurs, an exception is raised that includes the expression in its message. :param str expr: The string to be evaluated. :param str errorMessage: The string used for the raised exception message if an exception is thrown by eval, where ``{expr}`` will be replaced with the actual expression and ``{error}`` with the error message. :param bool emptyNamespace: By default a default namespace is provided including the symbols described above such as ``os.path``, ``pysys``, etc. Set this to True to start with a completely empty namespace with no symbols defined. :param dict[str,obj] extraNamespace: A dict of string names and Python object values to be included in the globals environment used to evaluate this string. .. versionadded:: 2.0 """ env = {} if emptyNamespace else globals() if extraNamespace: env = dict(env) for k,v in extraNamespace.items(): env[k] = v try: return eval(expr, env) except Exception as e: log.debug('Evaluation of %r failed: ', expr, exc_info=True) # nb: must use .replace() not .format() since errorMessage could include {...} string literals raise SafeEvalException(errorMessage.replace('{expr}', expr.replace('\n',' ').replace('\r','').strip()) .replace('{error}', '%s - %s'%(e.__class__.__name__, e or '<no message>')) .strip())