Hello community,

here is the log from the commit of package python-asteval for openSUSE:Factory 
checked in at 2019-09-16 10:50:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-asteval (Old)
 and      /work/SRC/openSUSE:Factory/.python-asteval.new.7948 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-asteval"

Mon Sep 16 10:50:48 2019 rev:5 rq:730700 version:0.9.15

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-asteval/python-asteval.changes    
2019-07-26 12:42:18.449857129 +0200
+++ /work/SRC/openSUSE:Factory/.python-asteval.new.7948/python-asteval.changes  
2019-09-16 10:50:50.763168785 +0200
@@ -1,0 +2,6 @@
+Fri Sep 13 12:09:15 UTC 2019 - Tomáš Chvátal <tchva...@suse.com>
+
+- Update to 0.9.15:
+  * some doc improvements
+
+-------------------------------------------------------------------

Old:
----
  asteval-0.9.14.tar.gz

New:
----
  asteval-0.9.15.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-asteval.spec ++++++
--- /var/tmp/diff_new_pack.iZ16ER/_old  2019-09-16 10:50:51.307168715 +0200
+++ /var/tmp/diff_new_pack.iZ16ER/_new  2019-09-16 10:50:51.311168714 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-asteval
-Version:        0.9.14
+Version:        0.9.15
 Release:        0
 Summary:        Safe, minimalistic evaluator of python expression using ast 
module
 License:        MIT

++++++ asteval-0.9.14.tar.gz -> asteval-0.9.15.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/PKG-INFO new/asteval-0.9.15/PKG-INFO
--- old/asteval-0.9.14/PKG-INFO 2019-05-24 13:56:02.000000000 +0200
+++ new/asteval-0.9.15/PKG-INFO 2019-08-22 19:02:25.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: asteval
-Version: 0.9.14
+Version: 0.9.15
 Summary: Safe, minimalistic evaluator of python expression using ast module
 Home-page: http://github.com/newville/asteval
 Author: Matthew Newville
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval/__init__.py 
new/asteval-0.9.15/asteval/__init__.py
--- old/asteval-0.9.14/asteval/__init__.py      2018-11-17 21:36:41.000000000 
+0100
+++ new/asteval-0.9.15/asteval/__init__.py      2019-08-22 17:59:19.000000000 
+0200
@@ -19,7 +19,7 @@
 
 from .asteval import Interpreter
 from .astutils import (NameFinder, valid_symbol_name,
-                       make_symbol_table, get_ast_names)
+                       make_symbol_table, get_ast_names, check_pyversion)
 from ._version import get_versions
 
 __all__ = ['Interpreter', 'NameFinder', 'valid_symbol_name',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval/_version.py 
new/asteval-0.9.15/asteval/_version.py
--- old/asteval-0.9.14/asteval/_version.py      2019-05-24 13:56:02.000000000 
+0200
+++ new/asteval-0.9.15/asteval/_version.py      2019-08-22 19:02:25.000000000 
+0200
@@ -8,11 +8,11 @@
 
 version_json = '''
 {
- "date": "2019-05-18T10:10:55-0500",
+ "date": "2019-08-22T11:55:58-0500",
  "dirty": false,
  "error": null,
- "full-revisionid": "464837213f66a3a7e66ecb14fff12bcb30a2d2db",
- "version": "0.9.14"
+ "full-revisionid": "2653ec36996153ce230d785ba47739ff007e9ffa",
+ "version": "0.9.15"
 }
 '''  # END VERSION_JSON
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval/asteval.py 
new/asteval-0.9.15/asteval/asteval.py
--- old/asteval-0.9.14/asteval/asteval.py       2018-11-17 21:36:41.000000000 
+0100
+++ new/asteval-0.9.15/asteval/asteval.py       2019-08-22 17:59:36.000000000 
+0200
@@ -44,11 +44,9 @@
 import inspect
 from sys import exc_info, stdout, stderr, version_info
 
-import six
-
 from .astutils import (UNSAFE_ATTRS, HAS_NUMPY, make_symbol_table, numpy,
                        op2func, ExceptionHolder, ReturnedNone,
-                       valid_symbol_name)
+                       valid_symbol_name, check_pyversion)
 
 builtins = __builtins__
 if not isinstance(builtins, dict):
@@ -62,7 +60,8 @@
              'pass', 'print', 'raise', 'repr', 'return', 'slice', 'str',
              'subscript', 'try', 'tuple', 'unaryop', 'while']
 
-ERR_MAX_TIME = "Execution exceeded time limit, max runtime is {}s"
+
+PY3 = check_pyversion()
 
 class Interpreter(object):
     """create an asteval Interpreter: a restricted, simplified interpreter
@@ -106,8 +105,6 @@
         whether to support `raise`.
     no_print : bool
         whether to support `print`.
-    max_time : float
-        deprecated, unreliable. max_time will be dropped soon. (default 86400)
     readonly_symbols : iterable or `None`
         symbols that the user can not assign to
     builtins_readonly : bool
@@ -117,7 +114,6 @@
     -----
     1. setting `minimal=True` is equivalent to setting all
        `no_***` options to `True`.
-    2. max_time is not reliable and no longer supported -- the keyword will be 
dropped soon.
     """
 
     def __init__(self, symtable=None, usersyms=None, writer=None,
@@ -125,7 +121,7 @@
                  no_if=False, no_for=False, no_while=False, no_try=False,
                  no_functiondef=False, no_ifexp=False, no_listcomp=False,
                  no_augassign=False, no_assert=False, no_delete=False,
-                 no_raise=False, no_print=False, max_time=86400,
+                 no_raise=False, no_print=False, max_time=None,
                  readonly_symbols=None, builtins_readonly=False):
 
         self.writer = writer or stdout
@@ -136,7 +132,10 @@
                 usersyms = {}
             symtable = make_symbol_table(use_numpy=use_numpy, **usersyms)
 
-        symtable['print'] = self._printer
+        if no_print:
+            symtable['print'] = self._nullprinter
+        else:
+            symtable['print'] = self._printer
 
         self.symtable = symtable
         self._interrupt = None
@@ -146,7 +145,6 @@
         self.retval = None
         self.lineno = 0
         self.start_time = time.time()
-        self.max_time = max_time
         self.use_numpy = HAS_NUMPY and use_numpy
 
         nodes = ALL_NODES[:]
@@ -280,8 +278,6 @@
         """Execute parsed Ast representation for an expression."""
         # Note: keep the 'node is None' test: internal code here may run
         #    run(None) and expect a None in return.
-        if time.time() - self.start_time > self.max_time:
-            raise RuntimeError(ERR_MAX_TIME.format(self.max_time))
         out = None
         if len(self.error) > 0:
             return out
@@ -595,25 +591,29 @@
         return val
 
     def on_compare(self, node):  # ('left', 'ops', 'comparators')
-        """comparison operators"""
+        """comparison operators, including chained comparisons (a<b<c)"""
         lval = self.run(node.left)
-        out = True
+        results = []
         for op, rnode in zip(node.ops, node.comparators):
             rval = self.run(rnode)
-            out = op2func(op)(lval, rval)
-            lval = rval
-            if self.use_numpy and isinstance(out, numpy.ndarray) and out.any():
-                break
-            elif not out:
+            ret = op2func(op)(lval, rval)
+            results.append(ret)
+            if (self.use_numpy and not isinstance(ret, numpy.ndarray)) and not 
ret:
                 break
+            lval = rval
+        if len(results) == 1:
+            return results[0]
+        else:
+            out = True
+            for r in results:
+                out = out and r
         return out
 
+
     def on_print(self, node):    # ('dest', 'values', 'nl')
         """Note: implements Python2 style print statement, not print()
         function.
-
         May need improvement....
-
         """
         dest = self.run(node.dest) or self.writer
         end = ''
@@ -623,6 +623,10 @@
         if out and len(self.error) == 0:
             self._printer(*out, file=dest, end=end)
 
+    def _nullprinter(self, *out, **kws):
+        """swallow print calls"""
+        pass
+
     def _printer(self, *out, **kws):
         """Generic print function."""
         flush = kws.pop('flush', True)
@@ -728,7 +732,7 @@
 
     def on_raise(self, node):    # ('type', 'inst', 'tback')
         """Raise statement: note difference for python 2 and 3."""
-        if version_info[0] == 3:
+        if PY3:
             excnode = node.exc
             msgnode = node.cause
         else:
@@ -755,7 +759,7 @@
             args = args + self.run(starargs)
 
         keywords = {}
-        if six.PY3 and func == print:
+        if PY3 and func == print:
             keywords['file'] = self.writer
 
         for key in node.keywords:
@@ -796,11 +800,10 @@
             keyval = self.run(node.args.args[idef+offset])
             kwargs.append((keyval, defval))
 
-        if version_info[0] == 3:
+        if PY3:
             args = [tnode.arg for tnode in node.args.args[:offset]]
         else:
             args = [tnode.id for tnode in node.args.args[:offset]]
-
         doc = None
         nb0 = node.body[0]
         if isinstance(nb0, ast.Expr) and isinstance(nb0.value, ast.Str):
@@ -808,11 +811,10 @@
 
         varkws = node.args.kwarg
         vararg = node.args.vararg
-        if version_info[0] == 3:
-            if isinstance(vararg, ast.arg):
-                vararg = vararg.arg
-            if isinstance(varkws, ast.arg):
-                varkws = varkws.arg
+        if PY3 and isinstance(vararg, ast.arg):
+            vararg = vararg.arg
+        if PY3 and isinstance(varkws, ast.arg):
+            varkws = varkws.arg
 
         self.symtable[node.name] = Procedure(node.name, self, doc=doc,
                                              lineno=self.lineno,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval/astutils.py 
new/asteval-0.9.15/asteval/astutils.py
--- old/asteval-0.9.14/asteval/astutils.py      2019-05-24 13:49:43.000000000 
+0200
+++ new/asteval-0.9.15/asteval/astutils.py      2019-08-22 18:04:11.000000000 
+0200
@@ -13,16 +13,27 @@
 from sys import exc_info, version_info
 from tokenize import NAME as tk_NAME
 
-if version_info >= (3, 0):
+def check_pyversion():
+    version_major, version_minor = version_info[0],  version_info[1]
+    if version_major == 3 and version_minor < 5:
+        raise SystemError("Python 3.0 to 3.4 are not supported")
+    if version_major == 2 and version_minor < 7:
+        raise SystemError("Python 2.6 or earlier are not supported")
+    return version_major == 3
+
+
+if version_info > (3, 4):
     from tokenize import tokenize as generate_tokens, ENCODING as tk_ENCODING
 else:
     from tokenize import generate_tokens
     tk_ENCODING = None
 
+
 HAS_NUMPY = False
 numpy = None
 try:
     import numpy
+    ndarr = numpy.ndarray
     HAS_NUMPY = True
 except ImportError:
     pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval.egg-info/PKG-INFO 
new/asteval-0.9.15/asteval.egg-info/PKG-INFO
--- old/asteval-0.9.14/asteval.egg-info/PKG-INFO        2019-05-24 
13:56:02.000000000 +0200
+++ new/asteval-0.9.15/asteval.egg-info/PKG-INFO        2019-08-22 
19:02:25.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: asteval
-Version: 0.9.14
+Version: 0.9.15
 Summary: Safe, minimalistic evaluator of python expression using ast module
 Home-page: http://github.com/newville/asteval
 Author: Matthew Newville
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval.egg-info/SOURCES.txt 
new/asteval-0.9.15/asteval.egg-info/SOURCES.txt
--- old/asteval-0.9.14/asteval.egg-info/SOURCES.txt     2019-05-24 
13:56:02.000000000 +0200
+++ new/asteval-0.9.15/asteval.egg-info/SOURCES.txt     2019-08-22 
19:02:25.000000000 +0200
@@ -12,7 +12,6 @@
 asteval.egg-info/PKG-INFO
 asteval.egg-info/SOURCES.txt
 asteval.egg-info/dependency_links.txt
-asteval.egg-info/requires.txt
 asteval.egg-info/top_level.txt
 doc/Makefile
 doc/api.rst
@@ -23,6 +22,4 @@
 doc/motivation.rst
 doc/_static/empty
 doc/_templates/indexsidebar.html
-tests/A.py
-tests/m.py
 tests/test_asteval.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/asteval.egg-info/requires.txt 
new/asteval-0.9.15/asteval.egg-info/requires.txt
--- old/asteval-0.9.14/asteval.egg-info/requires.txt    2019-05-24 
13:56:02.000000000 +0200
+++ new/asteval-0.9.15/asteval.egg-info/requires.txt    1970-01-01 
01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-numpy
-six
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/doc/basics.rst 
new/asteval-0.9.15/doc/basics.rst
--- old/asteval-0.9.14/doc/basics.rst   2018-09-29 15:06:59.000000000 +0200
+++ new/asteval-0.9.15/doc/basics.rst   2019-08-22 18:55:13.000000000 +0200
@@ -124,7 +124,7 @@
         sum += i*sqrt(*1.0)
         if i % 4 == 0:
             sum = sum + 1
-    print "sum = ", sum
+    print("sum = ", sum)
     """
     >>> aeval(code)
     sum =  114.049534067
@@ -161,7 +161,7 @@
 exceptions
 ===============
 
-asteval monitors and caches exceptions in the evaluated code.  Brief error
+Asteval monitors and caches exceptions in the evaluated code.  Brief error
 messages are printed (with Python's print statement or function, and so
 using standard output by default), and the full set of exceptions is kept
 in the :attr:`error` attribute of the :class:`Interpreter` instance.  This
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/doc/index.rst 
new/asteval-0.9.15/doc/index.rst
--- old/asteval-0.9.14/doc/index.rst    2018-09-29 15:06:59.000000000 +0200
+++ new/asteval-0.9.15/doc/index.rst    2019-08-22 18:34:29.000000000 +0200
@@ -13,42 +13,47 @@
 area of application is the evaluation of mathematical expressions. Because
 of this, mathematical functions from Python's :py:mod:`math` module are
 available, and a large number of functions from `numpy`_ will be available
-if it is installed on your system.
+if `numpy`_ is installed on your system.
 
-In addition to basic mathematical expressions, many parts of the Python
-language are supported by default, including array slicing and
-subscripting, if-then-else conditionals, while loops, for loops, try-except
-blocks, list comprehension, and user-defined functions.  All objects in the
-asteval interpreter are truly python objects, and all built-in data
-structures (strings, dictionaries, tuple, lists, numpy arrays), are
-supported. That is, the asteval mini-language will look and act very much
-like Python itself.
-
-Still, there are important differences and missing features compared to
-Python. Asteval is by no means an attempt to reproduce Python with its own
-:py:mod:`ast` module.  Some of the main differences and absences include:
+While the emphasis is on basic mathematical expressions, many features of the
+Python language are supported by default.  These features include array
+slicing and subscripting, if-then-else conditionals, while loops, for loops,
+try-except blocks, list comprehension, and user-defined functions.  All
+objects in the asteval interpreter are truly python objects, and all basic
+built-in data structures (strings, dictionaries, tuple, lists, numpy arrays)
+are supported.
+
+However, Asteval is by no means an attempt to reproduce Python with its own
+:py:mod:`ast` module.  There are important differences and missing features
+compared to Python.  Many of these absences are intentionally trying to make a
+safer version of :py:func:`eval`, while some are simply due to the reduced
+requirements for a small embedded min-language. Some of the main differences
+and absences include:
 
  1. Variable and function symbol names are held in a simple symbol
     table - a single dictionary - giving a flat namespace.
  2. creating classes is not allowed.
  3. importing modules is not allowed.
  4. function decorators, generators, yield, and lambda are not supported.
- 5. several builtins (:py:func:`eval`, :py:func:`execfile`,
+ 5. Many builtins (:py:func:`eval`, :py:func:`execfile`,
     :py:func:`getattr`, :py:func:`hasattr`, :py:func:`setattr`, and
     :py:func:`delattr`) are not allowed.
  6. Accessing several private object attributes that can provide access to
     the python interpreter are not allowed.
 
-The result of this makes asteval a decidedly restricted and limited language
-that is focused on mathematical calculations.
+The effect to make the asteval mini-language look and act very much like
+miniature version of Python itself focused on mathematical calculations, and
+with noticeable limitations.
 
-Because asteval is suitable for evaluating user-supplied input strings,
-safety against malicious user input is an important concern.  Asteval tries
-as hard as possible to prevent user-supplied input from crashing the Python
-interpreter or from returning exploitable parts of the Python interpreter.
-In this sense asteval is certainly safer than using :py:func:`eval`.
-However, asteval is an open source project written by volunteers, and we
-cannot guarantee that it is safe against malicious attacks.
+
+Because asteval is suitable for evaluating user-supplied input strings, safety
+against malicious or incompetent user input is an important concern.  Asteval
+tries as hard as possible to prevent user-supplied input from crashing the
+Python interpreter or from returning exploitable parts of the Python
+interpreter.  In this sense asteval is certainly safer than using
+:py:func:`eval`.  However, asteval is an open source project written by
+volunteers, and we cannot guarantee that it is completely safe against
+malicious attacks.
 
 .. toctree::
    :maxdepth: 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/doc/installation.rst 
new/asteval-0.9.15/doc/installation.rst
--- old/asteval-0.9.14/doc/installation.rst     2018-09-29 15:06:59.000000000 
+0200
+++ new/asteval-0.9.15/doc/installation.rst     2019-08-22 18:25:12.000000000 
+0200
@@ -9,17 +9,20 @@
 Requirements
 ~~~~~~~~~~~~~~~
 
-The asteval package is supported for use with Python 2.7, 3.5, and 3.6.
-The package may work for Python 2.6, and Python 3.4 or earlier, but no testing
-is done for these out-dated versions.  Asteval will make use of the `numpy`_
-module if available.
+Asteval is a pure python module with no required dependencies outside of the
+standard library.  Asteval will make use of the `numpy`_ module if available.
+
+Version 0.9.15 supports Python 2.7, Python 3.5 and higher.  It is tested with
+Python 2.7, 3.5, 3.6, and 3.7.
+
+Version 0.9.16 will drop support for Python 2.7.
 
 
 Download and Installation
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 The latest stable version of asteval is |release| and is available at `PyPI`_ 
or as
-a conda package.  That is, you should be able to install asteval with::
+a conda package.  You should be able to install asteval with::
 
    pip install asteval
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/doc/motivation.rst 
new/asteval-0.9.15/doc/motivation.rst
--- old/asteval-0.9.14/doc/motivation.rst       2018-09-29 15:06:59.000000000 
+0200
+++ new/asteval-0.9.15/doc/motivation.rst       2019-08-22 18:52:14.000000000 
+0200
@@ -6,62 +6,65 @@
 ########################
 
 The asteval module allows you to evaluate a large subset of the Python
-language from within a python program, without using :py:func:`eval`.  It
-is, in effect, a restricted version of Python's built-in :py:func:`eval`,
-forbidding several actions, and using using a simple dictionary as a flat
-namespace.  A completely fair question is: Why is this desirable?  That
-is, why not simply use :py:func:`eval`, or just use Python itself?
-
-The short answer is that sometimes you want to allow evaluation of user
-input, or expose a simple calculator inside a larger application.  For
-this, :py:func:`eval` is pretty scary, as it exposes *all* of Python, which
-makes user input difficult to trust.  Since asteval does not support the
-**import** statement (or many other constructs), user code cannot access
-the :py:mod:`os` and :py:mod:`sys` modules or any functions or classes
-outside the provided symbol table.
-
-Other missing features (modules, classes, lambda, yield, generators) are
-similarly motivated by a desire for a safer version of :py:func:`eval`.
-The idea for asteval is to make a simple procedural, mathematically
-oriented language that can be embedded into larger applications.
+language from within a python program, without using :py:func:`eval`.  It is,
+in effect, a restricted version of Python's built-in :py:func:`eval`,
+forbidding several actions, and using a simple dictionary as a flat namespace.
+A completely fair question is: Why is this desirable?  That is, why not simply
+use :py:func:`eval`, or just use Python itself?
+
+The short answer is that sometimes you want to allow evaluation of user input,
+or expose a simple (or even "scientific") calculator inside a larger
+application.  For this, :py:func:`eval` is pretty scary, as it exposes *all*
+of Python, which makes user input difficult to trust.  Since asteval does not
+support the **import** statement or many other constructs, user code cannot
+access the :py:mod:`os` and :py:mod:`sys` modules or any functions or classes
+outside those provided in the symbol table.
+
+Many of the other missing features (modules, classes, lambda, yield,
+generators) are similarly motivated by a desire for a safer version of
+:py:func:`eval`.  The idea for asteval is to make a simple procedural,
+mathematically oriented language that can be embedded into larger
+applications.
 
 In fact, the asteval module grew out the the need for a simple expression
 evaluator for scientific applications such as the `lmfit`_ and `xraylarch`_
-modules.  A first attempt using the pyparsing module worked but was
-error-prone and difficult to maintain.  It turned out that using the Python
-:py:mod:`ast` module is so easy that adding more complex programming
-constructs like conditionals, loops, exception handling, complex assignment
-and slicing, and even user-defined functions was fairly simple to
-implement.  Importantly, because parsing is done by the :py:mod:`ast`
-module, whole classes of implementation errors disappear.  Valid python
-expression will be parsed correctly and converted into an Abstract Syntax
-Tree.  Furthermore, the resulting AST is easy to walk through, greatly
-simplifying evaluation over any other approach.  What started as a desire
-for a simple expression evaluator grew into a quite useable procedural
-domain-specific language for mathematical applications.
-
-Asteval makes no claims about speed. Obviously,  evaluating the ast tree
-involves a lot of function calls, and will likely be slower than Python.
-In preliminary tests, it's about 4x slower than Python.  For certain use
-cases (see https://stackoverflow.com/questions/34106484), use of asteval
-and numpy can approach the speed of `eval` and the `numexpr` modules.
+modules.  An early attempt using the pyparsing module worked but was
+error-prone and difficult to maintain.  While the simplest of calculators or
+expressiona-evaluators is not hard with pyparsing, it turned out that using
+the Python :py:mod:`ast` module makes for a much easier and feature rich
+simple calculator scientific calculator, including slicing, complex numbers,
+keyword arguments to functions, etc.  In fact, it is so easy that adding more
+complex programming constructs like conditionals, loops, exception handling,
+and even user-defined functions was fairly simple to implement.  Importantly,
+because parsing is done by the :py:mod:`ast` module, whole classes of
+implementation errors disappear.  Valid python expression will be parsed
+correctly and converted into an Abstract Syntax Tree.  Furthermore, the
+resulting AST is easy to walk through, greatly simplifying evaluation over any
+other approach.  What started as a desire for a simple expression evaluator
+grew into a quite useable procedural domain-specific language for mathematical
+applications.
+
+Asteval makes no claims about speed. Evaluating the AST involves many function
+calls, which is going to be slower than Python.  In preliminary tests, it's
+about 4x slower than Python.  That said, for certain use cases (see
+https://stackoverflow.com/questions/34106484), use of asteval and numpy can
+approach the speed of `eval` and the `numexpr` modules.
 
 How Safe is asteval?
 =======================
 
-Asteval avoids the known exploits that make :py:func:`eval` dangerous. For
-reference, see, `Eval is really dangerous
-<http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html>`_ and
-the comments and links therein.  From this discussion it is apparent that
-not only is :py:func:`eval` unsafe, but that it is a difficult prospect to
-make any program that takes user input perfectly safe.  In particular, if a
-user can cause Python to crash with a segmentation fault, safety cannot be
-guaranteed.  Asteval explicitly forbids the exploits described in the above
-link, and works hard to prevent malicious code from crashing Python or
-accessing the underlying operating system.  That said, we cannot guarantee
-that asteval is completely safe from malicious code.  We claim only that it
-is safer than the builtin :py:func:`eval`, and that you might find it
-useful.
+Asteval avoids all of the exploits we know about that make :py:func:`eval`
+dangerous. For reference, see, `Eval is really dangerous
+<http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html>`_ and the
+comments and links therein.  From this discussion it is apparent that not only
+is :py:func:`eval` unsafe, but that it is a difficult prospect to make any
+program that takes user input perfectly safe.  In particular, if a user can
+cause Python to crash with a segmentation fault, safety cannot be guaranteed.
+Asteval explicitly forbids the exploits described in the above link, and works
+hard to prevent malicious code from crashing Python or accessing the
+underlying operating system.  That said, we cannot guarantee that asteval is
+completely safe from malicious code.  We claim only that it is safer than the
+builtin :py:func:`eval`, and that you might find it useful.
 
 Some of the things not allowed in the asteval interpreter for safety reasons 
include:
 
@@ -101,20 +104,23 @@
 
 can take a noticeable amount of CPU time.  It is not hard to come up with
 short program that would run for hundreds of years, which probably exceeds
-anyones threshold for an acceptable run-time.  As a very simple example, it
-is very hard to predict how long the expression `x**y**z` will take to run
-without knowing the values of `x`, `y`, and `z`.   In short, runtime cannot
-be determined lexically.
-
-Unfortunately, there is not a good way to check for a long-running
-calculation within a single Python process.  For example, the double
-exponential example above will be stuck deep inside C-code evaluated by the
-Python interpreter itself, and will not return or allow other threads to
-run until that calculation is done.  That is, from within a single process,
-there is not a foolproof way to tell `asteval` when a calculation has taken
-too long.  The most reliable way to limit run time is to have a second
-process watching the execution time of the asteval process and interrupt
-or kill it.
+anyones threshold for an acceptable run-time.  But there simply is not an
+obvious way to predict how long any code will take to run from the text of the
+code itself.  As a simple example, consider the expression `x**y**z`.  For
+values of `x`, `y`, and `z`.  For `x=y=z=5`, runtime will be well under 0.001
+seconds.  For `x=y=z=8`, runtime will still be under 1 sec.  For `x=8, y=9,
+z=9`, runtime will several seconds.  But for `x=y=z=9`, runtime may exceed 1
+hour on some machines.  In short, runtime cannot be determined lexically.
+
+This example also demonstrates there is not a good way to check for a
+long-running calculation within a single Python process.  That calculation is
+not stuck within the Python interpreter -- it is stuck deep inside C-code.
+called by the Python interpreter itself, and will not return or allow other
+threads to run until that calculation is done.  That is, from within a single
+process, there is not a foolproof way to tell `asteval` (or really, even
+Python) when a calculation has taken too long.  The most reliable way to limit
+run time is to have a second process watching the execution time of the
+asteval process and interrupt or kill it.
 
 For a limited range of problems, you can try to avoid asteval taking too
 long.  For example, you may try to limit the *recursion limit* when
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/setup.py new/asteval-0.9.15/setup.py
--- old/asteval-0.9.14/setup.py 2018-10-13 16:42:11.000000000 +0200
+++ new/asteval-0.9.15/setup.py 2019-08-22 17:10:28.000000000 +0200
@@ -12,7 +12,8 @@
 Expressions can be compiled into ast node for later evaluation,
 using the values in the symbol table current at evaluation time.
 """
-install_reqs = ['numpy', 'six']
+install_reqs = []
+test_reqs = ['pytest']
 
 setup(name='asteval',
       version=versioneer.get_version(),
@@ -26,6 +27,7 @@
       long_description=long_description,
       packages=['asteval'],
       install_requires=install_reqs,
+      tests_require=test_reqs,
       classifiers=['Intended Audience :: End Users/Desktop',
                    'Intended Audience :: Developers',
                    'Intended Audience :: Science/Research',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/tests/A.py 
new/asteval-0.9.15/tests/A.py
--- old/asteval-0.9.14/tests/A.py       2018-09-21 02:52:44.000000000 +0200
+++ new/asteval-0.9.15/tests/A.py       1970-01-01 01:00:00.000000000 +0100
@@ -1,18 +0,0 @@
-import sys
-from asteval import Interpreter
-aeval = Interpreter()
-
-script = """
-def tmp(x):
-    return tmp(x+1)
-##
-"""
-
-aeval(script)
-
-for rec_limit in (50, 100, 200, 500, 1000, 2000, 5000, 10000):
-    sys.setrecursionlimit(rec_limit)
-    aeval('tmp(33)')
-    msg = aeval.error_msg
-    if msg is not None:
-        print("rec limit=%d, length of error messge=%d" % (rec_limit,  
len(msg)))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/tests/m.py 
new/asteval-0.9.15/tests/m.py
--- old/asteval-0.9.14/tests/m.py       2018-09-21 02:33:45.000000000 +0200
+++ new/asteval-0.9.15/tests/m.py       1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +0,0 @@
-from asteval import Interpreter
-
-aeval = Interpreter()
-
-text = """
-def foo():
-    return foo()
-"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-0.9.14/tests/test_asteval.py 
new/asteval-0.9.15/tests/test_asteval.py
--- old/asteval-0.9.14/tests/test_asteval.py    2019-05-24 13:49:43.000000000 
+0200
+++ new/asteval-0.9.15/tests/test_asteval.py    2019-08-22 18:00:06.000000000 
+0200
@@ -9,27 +9,27 @@
 import unittest
 import pytest
 
-
 from sys import version_info
 from tempfile import NamedTemporaryFile
 
-PY3 = version_info[0] == 3
-PY33Plus = PY3 and version_info[1] >= 3
-PY35Plus = PY3 and version_info[1] >= 5
-PY35     = PY3 and version_info[1] == 5
-PY36Plus = PY3 and version_info[1] >= 6
+
+from asteval import NameFinder, Interpreter, make_symbol_table, check_pyversion
+
+PY3 = check_pyversion()
 
 if PY3:
-    # noinspection PyUnresolvedReferences
     from io import StringIO
 else:
-    # noinspection PyUnresolvedReferences
     from cStringIO import StringIO
 
-from asteval import NameFinder, Interpreter, make_symbol_table
 
-import numpy as np
-from numpy.testing import assert_allclose
+HAS_NUMPY = False
+try:
+    import numpy as np
+    from numpy.testing import assert_allclose
+    HAS_NUMPY = True
+except ImportError:
+    HAS_NUMPY = False
 
 
 class TestCase(unittest.TestCase):
@@ -89,20 +89,22 @@
     def isvalue(self, sym, val):
         """assert that a symboltable symbol has a particular value"""
         tval = self.interp.symtable[sym]
-        if isinstance(val, np.ndarray):
+        if HAS_NUMPY and isinstance(val, np.ndarray):
             assert_allclose(tval, val, rtol=0.01)
         else:
             assert(tval == val)
 
     def isnear(self, expr, val):
         tval = self.interp(expr)
-        assert_allclose(tval, val, rtol=1.e-4, atol=1.e-4)
+        if HAS_NUMPY:
+            assert_allclose(tval, val, rtol=1.e-4, atol=1.e-4)
+
 
     # noinspection PyUnresolvedReferences
     def istrue(self, expr):
         """assert that an expression evaluates to True"""
         val = self.interp(expr)
-        if isinstance(val, np.ndarray):
+        if HAS_NUMPY and isinstance(val, np.ndarray):
             val = np.all(val)
         return self.assertTrue(val)
 
@@ -110,7 +112,7 @@
     def isfalse(self, expr):
         """assert that an expression evaluates to False"""
         val = self.interp(expr)
-        if isinstance(val, np.ndarray):
+        if HAS_NUMPY and isinstance(val, np.ndarray):
             val = np.all(val)
         return self.assertFalse(val)
 
@@ -166,19 +168,21 @@
 
     def test_ndarray_index(self):
         """nd array indexing"""
-        self.interp("a_ndarray = 5*arange(20)")
-        assert(self.interp("a_ndarray[2]")  == 10)
-        assert(self.interp("a_ndarray[4]")  == 20)
+        if HAS_NUMPY:
+            self.interp("a_ndarray = 5*arange(20)")
+            assert(self.interp("a_ndarray[2]")  == 10)
+            assert(self.interp("a_ndarray[4]")  == 20)
 
     def test_ndarrayslice(self):
         """array slicing"""
-        self.interp("a_ndarray = arange(200).reshape(10, 20)")
-        self.istrue("a_ndarray[1:3,5:7] == array([[25,26], [45,46]])")
-        self.interp("y = arange(20).reshape(4, 5)")
-        self.istrue("y[:,3]  == array([3, 8, 13, 18])")
-        self.istrue("y[...,1]  == array([1, 6, 11, 16])")
-        self.interp("y[...,1] = array([2, 2, 2, 2])")
-        self.istrue("y[1,:] == array([5, 2, 7, 8, 9])")
+        if HAS_NUMPY:
+            self.interp("a_ndarray = arange(200).reshape(10, 20)")
+            self.istrue("a_ndarray[1:3,5:7] == array([[25,26], [45,46]])")
+            self.interp("y = arange(20).reshape(4, 5)")
+            self.istrue("y[:,3]  == array([3, 8, 13, 18])")
+            self.istrue("y[...,1]  == array([1, 6, 11, 16])")
+            self.interp("y[...,1] = array([2, 2, 2, 2])")
+            self.istrue("y[1,:] == array([5, 2, 7, 8, 9])")
 
     def test_while(self):
         """while loops"""
@@ -270,24 +274,41 @@
         """for loops"""
         self.interp(textwrap.dedent("""
             n=0
-            for i in arange(10):
+            for i in range(10):
                 n += i
             """))
         self.isvalue('n', 45)
 
         self.interp(textwrap.dedent("""
             n=0
-            for i in arange(10):
+            for i in range(10):
                 n += i
             else:
                 n = -1
             """))
         self.isvalue('n', -1)
 
+        if HAS_NUMPY:
+            self.interp(textwrap.dedent("""
+                n=0
+                for i in arange(10):
+                    n += i
+                """))
+            self.isvalue('n', 45)
+
+            self.interp(textwrap.dedent("""
+                n=0
+                for i in arange(10):
+                    n += i
+                else:
+                    n = -1
+                """))
+            self.isvalue('n', -1)
+
     def test_for_break(self):
         self.interp(textwrap.dedent("""
             n=0
-            for i in arange(10):
+            for i in range(10):
                 n += i
                 if n > 2:
                     break
@@ -295,6 +316,17 @@
                 n = -1
             """))
         self.isvalue('n', 3)
+        if HAS_NUMPY:
+            self.interp(textwrap.dedent("""
+                n=0
+                for i in arange(10):
+                    n += i
+                    if n > 2:
+                        break
+                else:
+                    n = -1
+                """))
+            self.isvalue('n', 3)
 
     def test_if(self):
         """runtime errors test"""
@@ -355,7 +387,7 @@
             yes = True
             no = False
             nottrue = False
-            a = arange(7)"""))
+            a = range(7)"""))
 
         self.istrue("yes")
         self.isfalse("no")
@@ -398,10 +430,11 @@
         self.isvalue("s1", "a string")
         self.interp('b = (1,2,3)')
         self.isvalue("b", (1, 2, 3))
-        self.interp('a = 1.*arange(10)')
-        self.isvalue("a", np.arange(10))
-        self.interp('a[1:5] = 1 + 0.5 * arange(4)')
-        self.isnear("a", np.array([0., 1., 1.5, 2., 2.5, 5., 6., 7., 8., 9.]))
+        if HAS_NUMPY:
+            self.interp('a = 1.*arange(10)')
+            self.isvalue("a", np.arange(10))
+            self.interp('a[1:5] = 1 + 0.5 * arange(4)')
+            self.isnear("a", np.array([0., 1., 1.5, 2., 2.5, 5., 6., 7., 8., 
9.]))
 
     def test_names(self):
         """names test"""
@@ -456,7 +489,7 @@
         self.interp("zero = 0")
         self.interp("astr ='a string'")
         self.interp("atup = ('a', 'b', 11021)")
-        self.interp("arr  = arange(20)")
+        self.interp("arr  = range(20)")
         for expr, errname in (('x = 1/zero', 'ZeroDivisionError'),
                               ('x = zero + nonexistent', 'NameError'),
                               ('x = zero + astr', 'TypeError'),
@@ -478,24 +511,25 @@
     # noinspection PyUnresolvedReferences
     def test_ndarrays(self):
         """simple ndarrays"""
-        self.interp('n = array([11, 10, 9])')
-        self.istrue("isinstance(n, ndarray)")
-        self.istrue("len(n) == 3")
-        self.isvalue("n", np.array([11, 10, 9]))
-        self.interp('n = arange(20).reshape(5, 4)')
-        self.istrue("isinstance(n, ndarray)")
-        self.istrue("n.shape == (5, 4)")
-        self.interp("myx = n.shape")
-        self.interp("n.shape = (4, 5)")
-        self.istrue("n.shape == (4, 5)")
-        self.interp("a = arange(20)")
-        self.interp("gg = a[1:13:3]")
-        self.isvalue('gg', np.array([1, 4, 7, 10]))
-        self.interp("gg[:2] = array([0,2])")
-        self.isvalue('gg', np.array([0, 2, 7, 10]))
-        self.interp('a, b, c, d = gg')
-        self.isvalue('c', 7)
-        self.istrue('(a, b, d) == (0, 2, 10)')
+        if HAS_NUMPY:
+            self.interp('n = array([11, 10, 9])')
+            self.istrue("isinstance(n, ndarray)")
+            self.istrue("len(n) == 3")
+            self.isvalue("n", np.array([11, 10, 9]))
+            self.interp('n = arange(20).reshape(5, 4)')
+            self.istrue("isinstance(n, ndarray)")
+            self.istrue("n.shape == (5, 4)")
+            self.interp("myx = n.shape")
+            self.interp("n.shape = (4, 5)")
+            self.istrue("n.shape == (4, 5)")
+            self.interp("a = arange(20)")
+            self.interp("gg = a[1:13:3]")
+            self.isvalue('gg', np.array([1, 4, 7, 10]))
+            self.interp("gg[:2] = array([0,2])")
+            self.isvalue('gg', np.array([0, 2, 7, 10]))
+            self.interp('a, b, c, d = gg')
+            self.isvalue('c', 7)
+            self.istrue('(a, b, d) == (0, 2, 10)')
 
     def test_binop(self):
         """test binary ops"""
@@ -532,7 +566,8 @@
         self.isnear('sin(pi/2)', 1)
         self.isnear('cos(pi/2)', 0)
         self.istrue('exp(0) == 1')
-        self.isnear('exp(1)', np.e)
+        if HAS_NUMPY:
+            self.isnear('exp(1)', np.e)
 
     def test_namefinder(self):
         """test namefinder"""
@@ -562,17 +597,18 @@
     # noinspection PyUnresolvedReferences
     def test_index_assignment(self):
         """test indexing / subscripting on assignment"""
-        self.interp('x = arange(10)')
-        self.interp('l = [1,2,3,4,5]')
-        self.interp('l[0] = 0')
-        self.interp('l[3] = -1')
-        self.isvalue('l', [0, 2, 3, -1, 5])
-        self.interp('l[0:2] = [-1, -2]')
-        self.isvalue('l', [-1, -2, 3, -1, 5])
-        self.interp('x[1] = 99')
-        self.isvalue('x', np.array([0, 99, 2, 3, 4, 5, 6, 7, 8, 9]))
-        self.interp('x[0:2] = [9,-9]')
-        self.isvalue('x', np.array([9, -9, 2, 3, 4, 5, 6, 7, 8, 9]))
+        if HAS_NUMPY:
+            self.interp('x = arange(10)')
+            self.interp('l = [1,2,3,4,5]')
+            self.interp('l[0] = 0')
+            self.interp('l[3] = -1')
+            self.isvalue('l', [0, 2, 3, -1, 5])
+            self.interp('l[0:2] = [-1, -2]')
+            self.isvalue('l', [-1, -2, 3, -1, 5])
+            self.interp('x[1] = 99')
+            self.isvalue('x', np.array([0, 99, 2, 3, 4, 5, 6, 7, 8, 9]))
+            self.interp('x[0:2] = [9,-9]')
+            self.isvalue('x', np.array([9, -9, 2, 3, 4, 5, 6, 7, 8, 9]))
 
     def test_reservedwords(self):
         """test reserved words"""
@@ -820,41 +856,23 @@
         self.check_error(None)
         self.interp("10**10001")
         self.check_error('RuntimeError')
-        self.interp("10**array([10000, 10000, 10000])")
-        self.check_error(None)
-        self.interp("10**array([10000, 10000, 10001])")
-        self.check_error('RuntimeError')
         self.interp("1<<1000")
         self.check_error(None)
         self.interp("1<<1001")
         self.check_error('RuntimeError')
-        self.interp("1<<array([1000, 1000, 1000])")
-        self.check_error(None)
-        self.interp("1<<array([1000, 1000, 1001])")
-        self.check_error('RuntimeError')
+
 
     def test_safe_open(self):
         self.interp('open("foo1", "wb")')
         self.check_error('RuntimeError')
         self.interp('open("foo2", "rb")')
-        self.check_error('FileNotFoundError' if PY33Plus else 'IOError')
+        self.check_error('FileNotFoundError' if PY3 else 'IOError')
         self.interp('open("foo3", "rb", 2<<18)')
         self.check_error('RuntimeError')
 
     def test_recursionlimit(self):
         self.interp("""def foo(): return foo()\nfoo()""")
-        if PY35Plus:
-            self.check_error('RecursionError')
-        else:
-            self.check_error('RuntimeError')
-
-    def test_runtime(self):
-        self.interp.max_time = 3
-        self.interp("""for x in range(2<<21): pass""")
-        self.check_error('RuntimeError', 'time limit')
-        self.interp("""while True: pass""")
-        self.check_error('RuntimeError', 'time limit')
-
+        self.check_error('RecursionError' if PY3 else 'RuntimeError')
 
     def test_kaboom(self):
         """ test Ned Batchelder's 'Eval really is dangerous' - Kaboom test 
(and related tests)"""
@@ -909,39 +927,40 @@
 
     def test_custom_symtable(self):
         "test making and using a custom symbol table"
-        def cosd(x):
-            "cos with angle in degrees"
-            return np.cos(np.radians(x))
-
-        def sind(x):
-            "sin with angle in degrees"
-            return np.sin(np.radians(x))
-
-        def tand(x):
-            "tan with angle in degrees"
-            return np.tan(np.radians(x))
-
-        sym_table = make_symbol_table(cosd=cosd, sind=sind, tand=tand)
-
-        aeval = Interpreter(symtable=sym_table)
-        aeval("x1 = sind(30)")
-        aeval("x2 = cosd(30)")
-        aeval("x3 = tand(45)")
-
-        x1 = aeval.symtable['x1']
-        x2 = aeval.symtable['x2']
-        x3 = aeval.symtable['x3']
-
-        assert_allclose(x1, 0.50,     rtol=0.001)
-        assert_allclose(x2, 0.866025, rtol=0.001)
-        assert_allclose(x3, 1.00,     rtol=0.001)
+
+        if HAS_NUMPY:
+            def cosd(x):
+                "cos with angle in degrees"
+                return np.cos(np.radians(x))
+
+            def sind(x):
+                "sin with angle in degrees"
+                return np.sin(np.radians(x))
+
+            def tand(x):
+                "tan with angle in degrees"
+                return np.tan(np.radians(x))
+
+            sym_table = make_symbol_table(cosd=cosd, sind=sind, tand=tand)
+
+            aeval = Interpreter(symtable=sym_table)
+            aeval("x1 = sind(30)")
+            aeval("x2 = cosd(30)")
+            aeval("x3 = tand(45)")
+
+            x1 = aeval.symtable['x1']
+            x2 = aeval.symtable['x2']
+            x3 = aeval.symtable['x3']
+
+            assert_allclose(x1, 0.50,     rtol=0.001)
+            assert_allclose(x2, 0.866025, rtol=0.001)
+            assert_allclose(x3, 1.00,     rtol=0.001)
 
     def test_readonly_symbols(self):
 
         def foo():
             return 31
 
-
         usersyms = {
             "a": 10,
             "b": 11,
@@ -989,8 +1008,29 @@
         assert(aeval2("abs(-8)") == 8)
 
 
-
-
+    def test_chained_compparisons(self):
+        self.interp('a = 7')
+        self.interp('b = 12')
+        self.interp('c = 19')
+        self.interp('d = 30')
+        self.assertTrue(self.interp('a < b < c < d'))
+        self.assertFalse(self.interp('a < b < c/88 < d'))
+        self.assertFalse(self.interp('a < b < c < d/2'))
+
+    def test_array_compparisons(self):
+        if HAS_NUMPY:
+            self.interp("sarr = arange(8)")
+            sarr = np.arange(8)
+            o1 = self.interp("sarr < 4.3")
+            assert(np.all(o1 == (sarr < 4.3)))
+            o1 = self.interp("sarr == 4")
+            assert(np.all(o1 == (sarr == 4)))
+
+    def test_minimal(self):
+        aeval = Interpreter(builtins_readonly=True, minimal=True)
+        aeval("a_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}")
+        self.assertTrue(aeval("a_dict['a'] == 1"))
+        self.assertTrue(aeval("a_dict['c'] == 3"))
 
 class TestCase2(unittest.TestCase):
     def test_stringio(self):


Reply via email to