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'])
