Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-asteval for openSUSE:Factory 
checked in at 2026-08-01 18:34:08
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-asteval (Old)
 and      /work/SRC/openSUSE:Factory/.python-asteval.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-asteval"

Sat Aug  1 18:34:08 2026 rev:24 rq:1368877 version:1.0.9

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-asteval/python-asteval.changes    
2026-01-22 15:19:31.623601881 +0100
+++ /work/SRC/openSUSE:Factory/.python-asteval.new.16738/python-asteval.changes 
2026-08-01 18:37:18.944324850 +0200
@@ -1,0 +2,27 @@
+Fri Jul 31 16:44:52 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.0.9 (bsc#1273147, CVE-2026-55244):
+  * This fixes some usage problem and is also meant to address
+    the securirty advisories https://github.com/lmfit/asteval/sec
+    urity/advisories/GHSA-89v8-rhwq-hf77
+  * and https://github.com/lmfit/asteval/security/advisories/GHSA
+    -9w56-46f6-3qhx
+  * Fix unpacking of nested tuples in comprehensions
+  * Fix error with lambda functions, so that calls from top level
+    should clear previous errors and exceptions should clear the
+    callstack before re-raising
+  * Add try/except for import of ctypes, so that this module is
+    not required from asteval to run
+  * Update PyPI stats link in README
+  * Remove the "exiting exceptions", 'SystemExit' and
+    'GeneratorExit' from the symbol table to remove the possibily
+    of such an exception in asteval from exiting the calling
+    application.
+  * Make exception handling of BaseExceptions that are not also
+    Exceptions (most notably, KeyboardInterrupt, which is not
+    removed from the symbol table) more explicit, converting
+    these to RuntimeError.
+  * Add 'ctypes', 'dump, and 'tofile' as unsafe attributes of
+    numpyndarrays, so that these cannot be acccessed.
+
+-------------------------------------------------------------------

Old:
----
  asteval-1.0.8.tar.gz

New:
----
  asteval-1.0.9.tar.gz

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

Other differences:
------------------
++++++ python-asteval.spec ++++++
--- /var/tmp/diff_new_pack.QLrVOP/_old  2026-08-01 18:37:19.568346320 +0200
+++ /var/tmp/diff_new_pack.QLrVOP/_new  2026-08-01 18:37:19.572346458 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-asteval
-Version:        1.0.8
+Version:        1.0.9
 Release:        0
 Summary:        Safe, minimalistic evaluator of python expression using ast 
module
 License:        MIT

++++++ asteval-1.0.8.tar.gz -> asteval-1.0.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/PKG-INFO new/asteval-1.0.9/PKG-INFO
--- old/asteval-1.0.8/PKG-INFO  2025-12-17 21:54:33.178054800 +0100
+++ new/asteval-1.0.9/PKG-INFO  2026-06-11 20:39:49.612932400 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: asteval
-Version: 1.0.8
+Version: 1.0.9
 Summary: Safe, minimalistic evaluator of python expression using ast module
 Author-email: Matthew Newville <[email protected]>
 License-Expression: MIT
@@ -55,7 +55,7 @@
    :target: https://pypi.org/project/asteval
 
 .. image:: https://img.shields.io/pypi/dm/asteval.svg
-   :target: https://pypi.org/project/asteval
+   :target: https://pypistats.org/packages/asteval
 
 .. image:: https://img.shields.io/badge/docs-read-brightgreen
    :target: https://lmfit.github.io/asteval/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/README.rst new/asteval-1.0.9/README.rst
--- old/asteval-1.0.8/README.rst        2025-12-17 20:39:37.000000000 +0100
+++ new/asteval-1.0.9/README.rst        2026-06-09 04:09:16.000000000 +0200
@@ -20,7 +20,7 @@
    :target: https://pypi.org/project/asteval
 
 .. image:: https://img.shields.io/pypi/dm/asteval.svg
-   :target: https://pypi.org/project/asteval
+   :target: https://pypistats.org/packages/asteval
 
 .. image:: https://img.shields.io/badge/docs-read-brightgreen
    :target: https://lmfit.github.io/asteval/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/asteval/asteval.py 
new/asteval-1.0.9/asteval/asteval.py
--- old/asteval-1.0.8/asteval/asteval.py        2025-12-11 22:40:31.000000000 
+0100
+++ new/asteval-1.0.9/asteval/asteval.py        2026-06-11 19:44:51.000000000 
+0200
@@ -71,6 +71,8 @@
     MINIMAL_CONFIG[_tnode] = False
     DEFAULT_CONFIG[_tnode] = True
 
+NORAISE = 'asteval will not raise'
+
 class Interpreter:
     """create an asteval Interpreter: a restricted, simplified interpreter
     of mathematical expressions using Python syntax.
@@ -244,8 +246,6 @@
             self.error_msg = msg
         elif len(msg) > 0:
             pass
-            # if err.exc is not None:
-            #     self.error_msg = f"{err.exc.__name__}: {msg}"
         if exc is None:
             exc = self.error[-1].exc
             if exc is None and len(self.error) > 0:
@@ -279,6 +279,9 @@
             self.raise_exception(None, exc=SyntaxError, expr=text)
         except Exception:
             self.raise_exception(None, exc=RuntimeError, expr=text)
+        except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+            self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
+
         out = ast.fix_missing_locations(out)
         return out
 
@@ -310,8 +313,6 @@
             handler = self.node_handlers[node.__class__.__name__.lower()]
         except KeyError:
             self.raise_exception(None, exc=NotImplementedError, expr=self.expr)
-
-
         # run the handler:  this will likely generate
         # recursive calls into this run method.
         try:
@@ -322,14 +323,12 @@
         except Exception:
             if with_raise and self.expr is not None:
                 self.raise_exception(node, expr=self.expr)
-
-
+        except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+            self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
         # avoid too many repeated error messages (yes, this needs to be "2")
         if len(self.error) > 2:
             self._remove_duplicate_errors()
 
-        return None
-
     def _remove_duplicate_errors(self):
         """remove duplicate exceptions"""
         error = [self.error[0]]
@@ -363,6 +362,9 @@
                 if show_errors:
                     print(errmsg, file=self.err_writer)
                 return None
+            except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+                self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
+
         else:
             node = expr
         try:
@@ -373,11 +375,13 @@
                 if len(self.error) > 0:
                     errmsg = self.error[-1].get_error()[1]
                 print(errmsg, file=self.err_writer)
+        except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+            self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
+
         if raise_errors and len(self.error) > 0:
             self._remove_duplicate_errors()
             err = self.error[-1]
             raise err.exc(err.get_error()[1])
-        return None
 
     @staticmethod
     def dump(node, **kw):
@@ -423,6 +427,9 @@
                 thismod = sys.modules[name]
             except Exception:
                 self.raise_exception(None, exc=ImportError, msg='Import Error')
+            except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+                self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
+
 
         if fromlist is None:
             if asname is not None:
@@ -782,47 +789,62 @@
             if hasattr(ctx, '__exit__'):
                 ctx.__exit__()
 
+    def _extract_names_from_target(self, target):
+        """Recursively extract all Name nodes from a target (Name or Tuple)"""
+        names = []
+        if target.__class__ == ast.Name:
+            names.append(target.id)
+        elif target.__class__ == ast.Tuple:
+            for elt in target.elts:
+                names.extend(self._extract_names_from_target(elt))
+        return names
+
+    def _target_to_structure(self, target):
+        """Convert AST target to a structure for unpacking (str or nested 
tuple)"""
+        if target.__class__ == ast.Name:
+            return target.id
+        elif target.__class__ == ast.Tuple:
+            return tuple([self._target_to_structure(elt) for elt in 
target.elts])
+
+    def _unpack_to_target(self, target_structure, value):
+        """Recursively unpack value and assign to symtable according to 
target_structure"""
+        if isinstance(target_structure, str):
+            self.symtable[target_structure] = value
+        elif isinstance(target_structure, tuple):
+            for target_elem, val_elem in zip(target_structure, value):
+                self._unpack_to_target(target_elem, val_elem)
 
     def _comp_save_syms(self, node):
         """find and save symbols that will be used in a comprehension"""
         saved_syms = {}
         for tnode in node.generators:
-            if tnode.target.__class__ == ast.Name:
-                if (not valid_symbol_name(tnode.target.id) or
-                    tnode.target.id in self.readonly_symbols):
-                    errmsg = f"invalid symbol name (reserved word?) 
{tnode.target.id}"
+            # Extract all names from the target (handles nested tuples)
+            names = self._extract_names_from_target(tnode.target)
+            for name in names:
+                if not valid_symbol_name(name) or name in 
self.readonly_symbols:
+                    errmsg = f"invalid symbol name (reserved word?) {name}"
                     self.raise_exception(tnode.target, exc=NameError, 
msg=errmsg)
-                if tnode.target.id in self.symtable:
-                    saved_syms[tnode.target.id] = 
copy.deepcopy(self._getsym(tnode.target))
-
-            elif tnode.target.__class__ == ast.Tuple:
-                for tval in tnode.target.elts:
-                    if tval.id in self.symtable:
-                        saved_syms[tval.id] = copy.deepcopy(self._getsym(tval))
+                if name in self.symtable:
+                    saved_syms[name] = copy.deepcopy(self.symtable.get(name))
         return saved_syms
 
 
     def do_generator(self, gnodes, node, out):
         """general purpose generator """
         gnode = gnodes[0]
-        nametype = True
-        target = None
-        if gnode.target.__class__ == ast.Name:
-            if (not valid_symbol_name(gnode.target.id) or
-                gnode.target.id in self.readonly_symbols):
-                errmsg = f"invalid symbol name (reserved word?) 
{gnode.target.id}"
+        # Validate target names and convert to structure for unpacking
+        target_names = self._extract_names_from_target(gnode.target)
+        for name in target_names:
+            if not valid_symbol_name(name) or name in self.readonly_symbols:
+                errmsg = f"invalid symbol name (reserved word?) {name}"
                 self.raise_exception(gnode.target, exc=NameError, msg=errmsg)
-            target = gnode.target.id
-        elif gnode.target.__class__ == ast.Tuple:
-            nametype = False
-            target = tuple([gval.id for gval in gnode.target.elts])
+
+        target_structure = self._target_to_structure(gnode.target)
 
         for val in self.run(gnode.iter):
-            if nametype and target is not None:
-                self.symtable[target] = val
-            else:
-                for telem, tval in zip(target, val):
-                    self.symtable[telem] = tval
+            # Unpack value to target structure
+            self._unpack_to_target(target_structure, val)
+
             add = True
             for cond in gnode.ifs:
                 add = add and self.run(cond)
@@ -897,11 +919,18 @@
         excnode = node.exc
         msgnode = node.cause
         out = self.run(excnode)
-        msg = ' '.join(out.args)
+        try:
+            msg = ' '.join(out.args)
+        except TypeError:
+            msg = ' '
         msg2 = self.run(msgnode)
         if msg2 not in (None, 'None'):
             msg = f"{msg:s}: {msg2:s}"
-        self.raise_exception(None, exc=out.__class__, msg=msg, expr='')
+        # Prevent KeyboardInterrupt, SystemExit, GeneratorExit from escaping 
the sandbox
+        if issubclass(out.__class__, Exception):
+            self.raise_exception(None, exc=out.__class__, msg=msg, expr='')
+        else:
+            self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{out.__name__}")
 
     def on_call(self, node):
         """Function execution."""
@@ -943,6 +972,8 @@
             msg = f"Error running function '{func_name}' with args '{args}'"
             msg = f"{msg} and kwargs {keywords}: {ex}"
             self.raise_exception(node, msg=msg)
+        except (KeyboardInterrupt, SystemExit, GeneratorExit) as exc:
+            self.raise_exception(node, exc=RuntimeError, msg=f"{NORAISE} 
{exc.__name__}")
         finally:
             if isinstance(func, Procedure):
                 self._calldepth -= 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/asteval/astutils.py 
new/asteval-1.0.9/asteval/astutils.py
--- old/asteval-1.0.8/asteval/astutils.py       2025-12-11 22:40:31.000000000 
+0100
+++ new/asteval-1.0.9/asteval/astutils.py       2026-06-11 19:44:51.000000000 
+0200
@@ -8,7 +8,6 @@
 import io
 import os
 import sys
-import ctypes
 import math
 import numbers
 import re
@@ -17,6 +16,10 @@
 from tokenize import NAME as tk_NAME
 from tokenize import tokenize as generate_tokens
 from string import Formatter
+try:
+    import ctypes
+except ImportError:
+    ctypes = None
 
 builtins = __builtins__
 if not isinstance(builtins, dict):
@@ -39,12 +42,7 @@
 
 # This is a necessary API but it's undocumented and moved around
 # between Python releases
-try:
-    from _string import formatter_field_name_split
-except ImportError:
-    def formatter_field_name_split(x):
-        return x._formatter_field_name_split()
-
+from _string import formatter_field_name_split
 
 
 MAX_EXPONENT = 10000
@@ -75,23 +73,27 @@
 
 # unsafe attributes for particular objects, by type
 UNSAFE_ATTRS_DTYPES = {str: ('format', 'format_map')}
+if HAS_NUMPY:
+    UNSAFE_ATTRS_DTYPES[numpy.ndarray] = ('ctypes', 'tofile', 'dump')
 
 # unsafe modules that may be exposed in other modules
 # but should be prevented from being accessed
-UNSAFE_MODULES = (io, os, sys, ctypes)
+UNSAFE_MODULES = [io, os, sys]
+if ctypes is not None:
+    UNSAFE_MODULES.append(ctypes)
 
 # inherit these from python's __builtins__
 FROM_PY = ('ArithmeticError', 'AssertionError', 'AttributeError',
            'BaseException', 'BufferError', 'BytesWarning',
            'DeprecationWarning', 'EOFError', 'EnvironmentError',
-           'Exception', 'False', 'FloatingPointError', 'GeneratorExit',
+           'Exception', 'False', 'FloatingPointError',
            'IOError', 'ImportError', 'ImportWarning', 'IndentationError',
            'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError',
            'MemoryError', 'NameError', 'None',
            'NotImplementedError', 'OSError', 'OverflowError',
            'ReferenceError', 'RuntimeError', 'RuntimeWarning',
            'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
-           'SystemExit', 'True', 'TypeError', 'UnboundLocalError',
+           'True', 'TypeError', 'UnboundLocalError',
            'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError',
            'UnicodeTranslateError', 'UnicodeWarning', 'ValueError',
            'Warning', 'ZeroDivisionError', 'abs', 'all', 'any', 'bin',
@@ -170,12 +172,11 @@
                  'atan': 'arctan', 'atan2': 'arctan2', 'atanh':
                  'arctanh', 'acosh': 'arccosh', 'asinh': 'arcsinh'}
 
+NUMPY_TABLE = {}
 if HAS_NUMPY:
     FROM_NUMPY = tuple(set(FROM_NUMPY))
     FROM_NUMPY = tuple(sym for sym in FROM_NUMPY if hasattr(numpy, sym))
     NUMPY_RENAMES = {sym: value for sym, value in NUMPY_RENAMES.items() if 
hasattr(numpy, value)}
-
-    NUMPY_TABLE = {}
     for sym in FROM_NUMPY:
         obj = getattr(numpy, sym, None)
         if obj is not None:
@@ -192,9 +193,6 @@
             if obj is not None:
                 NUMPY_TABLE[sym] = obj
 
-else:
-    NUMPY_TABLE = {}
-
 
 def _open(filename, mode='r', buffering=-1, encoding=None):
     """read only version of open()"""
@@ -301,10 +299,7 @@
         msg = f"no safe attribute '{attr}' for {repr(obj)}"
         raise_exc(node, exc=AttributeError, msg=msg)
     else:
-        try:
-            return getattr(obj, attr)
-        except AttributeError:
-            pass
+        return getattr(obj, attr, None)
 
 class SafeFormatter(Formatter):
     def __init__(self, raise_exc, node):
@@ -401,10 +396,7 @@
 
     def get_error(self):
         """Retrieve error data."""
-        try:
-            exc_name = self.exc.__name__
-        except AttributeError:
-            exc_name = str(self.exc)
+        exc_name = self.exc.__name__
         if exc_name in (None, 'None'):
             exc_name = 'UnknownError'
 
@@ -637,15 +629,15 @@
 
     def __call__(self, *args, **kwargs):
         """TODO: docstring in public method."""
-        topsym = self.__asteval__.symtable
-        if self.__asteval__.config.get('nested_symtable', False):
+        aeval = self.__asteval__
+        topsym = aeval.symtable
+        if aeval.config.get('nested_symtable', False):
             sargs = {'_main': topsym}
             sgroups = topsym.get('_searchgroups', None)
             if sgroups is not None:
                 for sxname in sgroups:
                     sargs[sxname] = topsym.get(sxname)
 
-
             symlocals = Group(name=f'symtable_{self.name}_', **sargs)
             symlocals._searchgroups = list(sargs.keys())
         else:
@@ -655,7 +647,6 @@
         nargs = len(args)
         nkws = len(kwargs)
         nargs_expected = len(self.__argnames__)
-
         # check for too few arguments, but the correct keyword given
         if (nargs < nargs_expected) and nkws > 0:
             for name in self.__argnames__[nargs:]:
@@ -678,13 +669,6 @@
                                       lineno=self.lineno)
 
         # check more args given than expected, varargs not given
-        if nargs != nargs_expected:
-            msg = None
-            if nargs < nargs_expected:
-                msg = f"not enough arguments for Procedure {self.name}()"
-                msg = f"{msg} (expected {nargs_expected}, got {nargs}"
-                self.__raise_exc__(None, exc=TypeError, msg=msg)
-
         if nargs > nargs_expected and self.__vararg__ is None:
             if nargs - nargs_expected > len(self.__kwargs__):
                 msg = f"too many arguments for {self.name}() expected at most"
@@ -722,36 +706,51 @@
             msg = f"incorrect arguments for Procedure {self.name}"
             self.__raise_exc__(None, msg=msg, lineno=self.lineno)
 
-        if self.__asteval__.config.get('nested_symtable', False):
-            save_symtable = self.__asteval__.symtable
-            self.__asteval__.symtable = symlocals
+        if aeval.config.get('nested_symtable', False):
+            save_symtable = aeval.symtable
+            aeval.symtable = symlocals
         else:
-            save_symtable = self.__asteval__.symtable.copy()
-            self.__asteval__.symtable.update(symlocals)
+            save_symtable = aeval.symtable.copy()
+            aeval.symtable.update(symlocals)
 
-        self.__asteval__.retval = None
-        self.__asteval__._calldepth += 1
+        aeval.retval = None
+        # if top-level function/lambda call, and error is not None, clear 
errors.
+        if aeval._calldepth == 0 and len(aeval.error)  > 0:
+            aeval.prev_error = [e for e in aeval.error]
+            aeval.error = []
+        aeval._calldepth += 1
         retval = None
 
         # evaluate script of function
-        self.__asteval__.code_text.append(self.__text__)
+        aeval.code_text.append(self.__text__)
         for node in self.__body__:
-            out = self.__asteval__.run(node, lineno=node.lineno)
-            if len(self.__asteval__.error) > 0:
-                break
+            try:
+                out = aeval.run(node, lineno=node.lineno,
+                                          with_raise=True)
+            except Exception as exc:
+                aeval.symtable = save_symtable
+                aeval.code_text.pop()
+                aeval._calldepth -= 1
+                aeval._interrupt = None
+                raise exc
+
             if self.__is_lambda__:
                 retval = out
                 break
-            if self.__asteval__.retval is not None:
-                retval = self.__asteval__.retval
-                self.__asteval__.retval = None
+
+            if len(aeval.error) > 0:
+                break
+
+            if aeval.retval is not None:
+                retval = aeval.retval
+                aeval.retval = None
                 if retval is ReturnedNone:
                     retval = None
                 break
 
-        self.__asteval__.symtable = save_symtable
-        self.__asteval__.code_text.pop()
-        self.__asteval__._calldepth -= 1
-        self.__asteval__._interrupt = None
+        aeval.symtable = save_symtable
+        aeval.code_text.pop()
+        aeval._calldepth -= 1
+        aeval._interrupt = None
         symlocals = None
         return retval
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/asteval/version.py 
new/asteval-1.0.9/asteval/version.py
--- old/asteval-1.0.8/asteval/version.py        2025-12-17 21:54:32.000000000 
+0100
+++ new/asteval-1.0.9/asteval/version.py        2026-06-11 20:39:49.000000000 
+0200
@@ -1,5 +1,6 @@
-# file generated by setuptools-scm
+# file generated by vcs-versioning
 # don't change, don't track in version control
+from __future__ import annotations
 
 __all__ = [
     "__version__",
@@ -10,25 +11,14 @@
     "commit_id",
 ]
 
-TYPE_CHECKING = False
-if TYPE_CHECKING:
-    from typing import Tuple
-    from typing import Union
-
-    VERSION_TUPLE = Tuple[Union[int, str], ...]
-    COMMIT_ID = Union[str, None]
-else:
-    VERSION_TUPLE = object
-    COMMIT_ID = object
-
 version: str
 __version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
-commit_id: COMMIT_ID
-__commit_id__: COMMIT_ID
+__version_tuple__: tuple[int | str, ...]
+version_tuple: tuple[int | str, ...]
+commit_id: str | None
+__commit_id__: str | None
 
-__version__ = version = '1.0.8'
-__version_tuple__ = version_tuple = (1, 0, 8)
+__version__ = version = '1.0.9'
+__version_tuple__ = version_tuple = (1, 0, 9)
 
-__commit_id__ = commit_id = 'gabf86adfa'
+__commit_id__ = commit_id = 'g0b359d0b0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/asteval.egg-info/PKG-INFO 
new/asteval-1.0.9/asteval.egg-info/PKG-INFO
--- old/asteval-1.0.8/asteval.egg-info/PKG-INFO 2025-12-17 21:54:32.000000000 
+0100
+++ new/asteval-1.0.9/asteval.egg-info/PKG-INFO 2026-06-11 20:39:49.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: asteval
-Version: 1.0.8
+Version: 1.0.9
 Summary: Safe, minimalistic evaluator of python expression using ast module
 Author-email: Matthew Newville <[email protected]>
 License-Expression: MIT
@@ -55,7 +55,7 @@
    :target: https://pypi.org/project/asteval
 
 .. image:: https://img.shields.io/pypi/dm/asteval.svg
-   :target: https://pypi.org/project/asteval
+   :target: https://pypistats.org/packages/asteval
 
 .. image:: https://img.shields.io/badge/docs-read-brightgreen
    :target: https://lmfit.github.io/asteval/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/doc/basics.rst 
new/asteval-1.0.9/doc/basics.rst
--- old/asteval-1.0.8/doc/basics.rst    2025-12-11 22:40:31.000000000 +0100
+++ new/asteval-1.0.9/doc/basics.rst    2026-06-11 19:44:57.000000000 +0200
@@ -78,28 +78,27 @@
 
 At startup, many symbols are loaded into the symbol table from Python's
 builtins and the ``math`` module.  The builtins include several basic Python
-functions:
+functions and values:
 
     abs, all, any, bin, bool, bytearray, bytes, chr, complex,
     dict, dir, divmod, enumerate, filter, float, format,
     frozenset, hash, hex, id, int, isinstance, len, list, map,
     max, min, oct, ord, pow, range, repr, reversed, round,
-    set, slice, sorted, str, sum, tuple, zip
+    set, slice, sorted, str, sum, tuple, zip, False, True, 1j
 
 and a large number of named exceptions:
 
-    ArithmeticError, AssertionError, AttributeError,
-    BaseException, BufferError, BytesWarning, DeprecationWarning,
-    EOFError, EnvironmentError, Exception, False,
-    FloatingPointError, GeneratorExit, IOError, ImportError,
-    ImportWarning, IndentationError, IndexError, KeyError,
-    KeyboardInterrupt, LookupError, MemoryError, NameError, None,
-    NotImplemented, NotImplementedError, OSError, OverflowError,
+    ArithmeticError, AssertionError, AttributeError, BaseException,
+    BufferError, BytesWarning, DeprecationWarning, EOFError,
+    EnvironmentError, Exception, FloatingPointError, IOError,
+    ImportError, ImportWarning, IndentationError, IndexError,
+    KeyError, KeyboardInterrupt, LookupError, MemoryError, NameError,
+    None, NotImplemented, NotImplementedError, OSError, OverflowError,
     ReferenceError, RuntimeError, RuntimeWarning, StopIteration,
-    SyntaxError, SyntaxWarning, SystemError, SystemExit, True,
-    TypeError, UnboundLocalError, UnicodeDecodeError,
-    UnicodeEncodeError, UnicodeError, UnicodeTranslateError,
-    UnicodeWarning, ValueError, Warning, ZeroDivisionError
+    SyntaxError, SyntaxWarning, SystemError, TypeError,
+    UnboundLocalError, UnicodeDecodeError, UnicodeEncodeError,
+    UnicodeError, UnicodeTranslateError, UnicodeWarning, ValueError,
+    Warning, ZeroDivisionError
 
 
 The symbols imported from Python's ``math`` module include:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/doc/motivation.rst 
new/asteval-1.0.9/doc/motivation.rst
--- old/asteval-1.0.8/doc/motivation.rst        2025-12-17 20:44:56.000000000 
+0100
+++ new/asteval-1.0.9/doc/motivation.rst        2026-06-11 19:44:57.000000000 
+0200
@@ -127,6 +127,13 @@
 user-defined functions ("Procedures"), and one with f-string
 formatting.  Both of these were fixed for version 1.0.6.
 
+In 2026, for version 1.0.9, the abiilty to raise some kinds exceptions
+(including `SystemExit`, `KeyboardInterrupt`, and a few others) that
+could cause the calling process to exit were either removed from the
+symbol table completely or captured to raise a more benign
+`RuntimeError` instead.  In addition, for Numpy ndarrays, the `ctypes`
+attribute and `tofile` and `dump` methods were made inaccessible.
+
 
 Avoiding resource hogging
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/asteval-1.0.8/tests/test_asteval.py 
new/asteval-1.0.9/tests/test_asteval.py
--- old/asteval-1.0.8/tests/test_asteval.py     2025-12-11 22:40:31.000000000 
+0100
+++ new/asteval-1.0.9/tests/test_asteval.py     2026-06-11 19:44:51.000000000 
+0200
@@ -850,6 +850,32 @@
     check_error(interp, 'SyntaxError')
 
 @pytest.mark.parametrize("nested", [False, True])
+def test_comprehension_tuple_unpacking(nested):
+    """test comprehensions with tuple unpacking including nested tuples"""
+    interp = make_interpreter(nested_symtable=nested)
+
+    # Simple tuple unpacking
+    interp(textwrap.dedent("""
+            pairs = [(1, 2), (3, 4), (5, 6)]
+            result = [a + b for a, b in pairs]
+            """))
+    isvalue(interp, 'result', [3, 7, 11])
+
+    # Nested tuple unpacking in dict comprehension
+    interp(textwrap.dedent("""
+            data = enumerate(zip(range(3), 'abc'))
+            d = {index: b for index, (a, b) in data}
+            """))
+    isvalue(interp, 'd', {0: 'a', 1: 'b', 2: 'c'})
+
+    # Deeply nested tuple unpacking
+    interp(textwrap.dedent("""
+            data = [(1, (2, (3, 4))), (5, (6, (7, 8)))]
+            result = [a + b + c + d for a, (b, (c, d)) in data]
+            """))
+    isvalue(interp, 'result', [10, 26])
+
[email protected]("nested", [False, True])
 def test_ifexp(nested):
     """test if expressions"""
     interp = make_interpreter(nested_symtable=nested)
@@ -1596,7 +1622,6 @@
     assert len(interp.error) == 1
     assert interp.error[0].exc == NameError
 
-
 @pytest.mark.parametrize("nested", [False, True])
 def test_delete_slice(nested):
     """ test that a slice can be deleted"""
@@ -1612,7 +1637,6 @@
         interp.run("del g.dlist[4:7]")
         assert interp("g.dlist") == [1, 3, 5, 7, 15, 17, 19, 21]
 
-
 @pytest.mark.parametrize("nested", [False, True])
 def test_unsafe_procedure_access(nested):
     """
@@ -1738,5 +1762,85 @@
     assert x == 4
     assert y == 4
 
+
[email protected]("nested", [False, True])
+def test_lambda_exception_cleared(nested):
+    """ an exception in a function or lambda should not
+        preveent the function or lambda from being used GH #148
+    """
+    interp = make_interpreter(nested_symtable=nested)
+    func = interp('lambda x: 1.0/x')
+    a4 = func(4)
+    assert a4 > 0.249998
+    assert a4 < 0.250002
+
+    a1 = func(1.0)
+    assert a1 > 0.999998
+    assert a1 < 1.000002
+
+    exc = None
+    try:
+        func(0)
+    except ZeroDivisionError as e:
+        exc = e
+    except Exception as e:
+        exc = e
+    assert isinstance(exc, ZeroDivisionError)
+
+    a2 = func(2)
+    assert a2 > 0.499998
+    assert a2 < 0.500002
+
[email protected]("nested", [False, True])
+def test_exiting_exceptions(nested):
+    "test that SystemExit, GeneratorExit or not allowed"
+    interp = make_interpreter(nested_symtable=nested)
+    interp("x = 23")
+    interp("raise SystemExit")
+    check_error(interp, 'NameError')
+    interp("x = 25")
+    interp("raise GeneratorExit")
+    check_error(interp, 'NameError')
+    interp.parse("raise GeneratorExit")
+    check_error(interp, 'NameError')
+
+    interp("x = 30")
+    interp("raise KeyboardInterrupt")
+    check_error(interp, 'RuntimeError')
+    interp("x = 33")
+    interp("raise BaseException")
+    check_error(interp, 'RuntimeError')
+
+
[email protected]("nested", [False, True])
+def test_unsafe_ndarray_attrs(nested):
+    "test that ndarrays cannot access ctypes, dump, and tofile"
+    if HAS_NUMPY:
+        interp = make_interpreter(nested_symtable=nested)
+        interp("arr = arange(100)/5.0")
+        interp("y = arr.ctypes")
+        check_error(interp, 'AttributeError')
+        interp("y = 'clear'")
+        interp("arr.dump('foo')")
+        check_error(interp, 'AttributeError')
+        interp("y = 'clear'")
+        interp("arr.tofile('file.out')")
+        check_error(interp, 'AttributeError')
+
+
[email protected]("nested", [False, True])
+def test_type_returns_string(nested):
+    "test that 'type(x)' returns a string, not a type"
+    interp = make_interpreter(nested_symtable=nested)
+    interp("d1 = {'a': '3', 'b': 4, 'c': 5}")
+    interp("t1 = type(d1)")
+    interp("t2 = type(None)")
+    tval = interp.symtable.get('t1')
+    assert isinstance(tval, str)
+
+    tval = interp.symtable.get('t2')
+    assert isinstance(tval, str)
+
+
 if __name__ == '__main__':
     pytest.main(['-v', '-x', '-s'])

Reply via email to