Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-simpleeval for 
openSUSE:Factory checked in at 2024-01-03 12:23:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-simpleeval (Old)
 and      /work/SRC/openSUSE:Factory/.python-simpleeval.new.28375 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-simpleeval"

Wed Jan  3 12:23:57 2024 rev:6 rq:1135607 version:0.9.13

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-simpleeval/python-simpleeval.changes      
2022-02-23 16:27:09.659510357 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-simpleeval.new.28375/python-simpleeval.changes
   2024-01-03 12:23:57.721228048 +0100
@@ -1,0 +2,12 @@
+Fri Dec 29 09:15:41 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 0.9.13:
+  * Better handling of empty strings passed as input.
+  * Fix the shift safe number issue from 0.9.12
+  * More minor pylint / etc fixes / cleanups (general code
+    quality)
+  * separate `.parse` from #115
+  * Allow setting up completely empty `{}` operators / functions
+  * Add extra bit-ops from #87
+
+-------------------------------------------------------------------

Old:
----
  simpleeval-0.9.12.tar.gz

New:
----
  simpleeval-0.9.13.tar.gz

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

Other differences:
------------------
++++++ python-simpleeval.spec ++++++
--- /var/tmp/diff_new_pack.j1dpzp/_old  2024-01-03 12:23:58.365251579 +0100
+++ /var/tmp/diff_new_pack.j1dpzp/_new  2024-01-03 12:23:58.365251579 +0100
@@ -1,7 +1,7 @@
 #
 # spec file
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 # Copyright (c) 2015 Dr. Axel Braun
 #
 # All modifications and additions to the file contributed by third parties
@@ -20,7 +20,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define modname simpleeval
 Name:           python-%{modname}
-Version:        0.9.12
+Version:        0.9.13
 Release:        0
 Summary:        A simple, safe single expression evaluator library
 License:        MIT

++++++ simpleeval-0.9.12.tar.gz -> simpleeval-0.9.13.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/Makefile 
new/simpleeval-0.9.13/Makefile
--- old/simpleeval-0.9.12/Makefile      2022-01-15 17:00:46.000000000 +0100
+++ new/simpleeval-0.9.13/Makefile      2023-02-17 09:21:11.000000000 +0100
@@ -24,6 +24,8 @@
 lint:
        black --check --diff simpleeval.py test_simpleeval.py
        isort --check-only --diff simpleeval.py test_simpleeval.py
+       pylint simpleeval.py test_simpleeval.py
+       mypy simpleeval.py test_simpleeval.py
 
 format:
        black simpleeval.py test_simpleeval.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/PKG-INFO 
new/simpleeval-0.9.13/PKG-INFO
--- old/simpleeval-0.9.12/PKG-INFO      2022-01-15 18:31:14.599136400 +0100
+++ new/simpleeval-0.9.13/PKG-INFO      2023-02-17 09:24:18.891371300 +0100
@@ -1,13 +1,11 @@
 Metadata-Version: 2.1
 Name: simpleeval
-Version: 0.9.12
+Version: 0.9.13
 Summary: A simple, safe single expression evaluator library.
 Home-page: https://github.com/danthedeckie/simpleeval
 Author: Daniel Fairhead
 Author-email: [email protected]
-License: UNKNOWN
 Keywords: eval,simple,expression,parse,ast
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
@@ -31,7 +29,14 @@
    :target: https://badge.fury.io/py/simpleeval
    :alt: PyPI Version
 
-A quick single file library for easily adding evaluatable expressions into
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+
+.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
+   :target: https://github.com/PyCQA/pylint
+
+
+A single file library for easily adding evaluatable expressions into
 python projects.  Say you want to allow a user to set an alarm volume, which
 could depend on the time of day, alarm level, how many previous alarms had gone
 off, and if there is music playing at the time.
@@ -39,8 +44,9 @@
 Or if you want to allow simple formulae in a web application, but don't want to
 give full eval() access, or don't want to run in javascript on the client side.
 
-It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
-even just a single file you can dump into a project.
+It's deliberately trying to stay simple to use and not have millions of 
features,
+pull it in from PyPI (pip or easy_install), or even just a single file you can 
dump
+into a project.
 
 Internally, it's using the amazing python ``ast`` module to parse the
 expression, which allows very fine control of what is and isn't allowed.  It
@@ -55,7 +61,7 @@
 
 You should be aware of this when deploying in a public setting.
 
-The defaults are pretty locked down and basic, and it's very easy to add
+The defaults are pretty locked down and basic, and it's easy to add
 whatever extra specific functionality you need (your own functions,
 variable/name lookup, etc).
 
@@ -149,23 +155,36 @@
 |        | ``"spam" in "my breakfast"``       |
 |        | -> ``False``                       |
 +--------+------------------------------------+
+| ``^``  | "bitwise exclusive OR" (xor)       |
+|        | ``62 ^ 20`` -> ``42``              |
++--------+------------------------------------+
+| ``|``  | "bitwise OR"                       |
+|        | ``8 | 34`` -> ``42``               |
++--------+------------------------------------+
+| ``&``  | "bitwise AND"                      |
+|        | ``100 & 63`` -> ``36``             |
++--------+------------------------------------+
+| ``~``  | "bitwise invert"                   |
+|        | ``~ -43`` -> ``42``                |
++--------+------------------------------------+
 
 
-The ``^`` operator is notably missing - not because it's hard, but because it
-is often mistaken for a exponent operator, not the bitwise operation that it is
-in python.  It's trivial to add back in again if you wish (using the class
-based evaluator explained below):
+The ``^`` operator is often mistaken for a exponent operator, not the bitwise 
+operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you 
can
+replace the operator like this:
 
 .. code-block:: python
 
     >>> import ast
-    >>> import operator
+    >>> from simpleeval import safe_power
 
     >>> s = SimpleEval()
-    >>> s.operators[ast.BitXor] = operator.xor
+    >>> s.operators[ast.BitXor] = safe_power
+
+    >>> s.eval("3 ^ 2")
+    9
 
-    >>> s.eval("2 ^ 10")
-    8
+for example.
 
 Limited Power
 ~~~~~~~~~~~~~
@@ -305,6 +324,22 @@
 
     # and so on...
 
+One useful feature of using the ``SimpleEval`` object is that you can parse an 
expression
+once, and then evaluate it mulitple times using different ``names``:
+
+.. code-block:: python
+
+    # Set up & Cache the parse tree:
+    expression = "foo + bar"
+    parsed = s.parse(expression)
+
+    # evaluate the expression multiple times:
+    for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
+        s.names = names
+        print(s.eval(expression, previously_parsed=parsed))
+
+for instance.  This may help with performance.
+
 You can assign / edit the various options of the ``SimpleEval`` object if you
 want to.  Either assign them during creation (like the ``simple_eval``
 function)
@@ -418,11 +453,18 @@
 
 (requires ``entr``) 
 
+I'm trying to keep the codebase relatively clean with Black, isort, pylint & 
mypy.
+See::
+
+    $ make format
+
+and::
+
+    $ make lint
+
 BEWARE
 ------
 
-I've done the best I can with this library - but there's no warrenty, no 
guarentee, nada.  A lot of
+I've done the best I can with this library - but there's no warranty, no 
guarantee, nada.  A lot of
 very clever people think the whole idea of trying to sandbox CPython is 
impossible.  Read the code
 yourself, and use it at your own risk.
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/README.rst 
new/simpleeval-0.9.13/README.rst
--- old/simpleeval-0.9.12/README.rst    2022-01-15 16:40:37.000000000 +0100
+++ new/simpleeval-0.9.13/README.rst    2023-02-17 09:23:37.000000000 +0100
@@ -13,7 +13,14 @@
    :target: https://badge.fury.io/py/simpleeval
    :alt: PyPI Version
 
-A quick single file library for easily adding evaluatable expressions into
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+
+.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
+   :target: https://github.com/PyCQA/pylint
+
+
+A single file library for easily adding evaluatable expressions into
 python projects.  Say you want to allow a user to set an alarm volume, which
 could depend on the time of day, alarm level, how many previous alarms had gone
 off, and if there is music playing at the time.
@@ -21,8 +28,9 @@
 Or if you want to allow simple formulae in a web application, but don't want to
 give full eval() access, or don't want to run in javascript on the client side.
 
-It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
-even just a single file you can dump into a project.
+It's deliberately trying to stay simple to use and not have millions of 
features,
+pull it in from PyPI (pip or easy_install), or even just a single file you can 
dump
+into a project.
 
 Internally, it's using the amazing python ``ast`` module to parse the
 expression, which allows very fine control of what is and isn't allowed.  It
@@ -37,7 +45,7 @@
 
 You should be aware of this when deploying in a public setting.
 
-The defaults are pretty locked down and basic, and it's very easy to add
+The defaults are pretty locked down and basic, and it's easy to add
 whatever extra specific functionality you need (your own functions,
 variable/name lookup, etc).
 
@@ -131,23 +139,36 @@
 |        | ``"spam" in "my breakfast"``       |
 |        | -> ``False``                       |
 +--------+------------------------------------+
+| ``^``  | "bitwise exclusive OR" (xor)       |
+|        | ``62 ^ 20`` -> ``42``              |
++--------+------------------------------------+
+| ``|``  | "bitwise OR"                       |
+|        | ``8 | 34`` -> ``42``               |
++--------+------------------------------------+
+| ``&``  | "bitwise AND"                      |
+|        | ``100 & 63`` -> ``36``             |
++--------+------------------------------------+
+| ``~``  | "bitwise invert"                   |
+|        | ``~ -43`` -> ``42``                |
++--------+------------------------------------+
 
 
-The ``^`` operator is notably missing - not because it's hard, but because it
-is often mistaken for a exponent operator, not the bitwise operation that it is
-in python.  It's trivial to add back in again if you wish (using the class
-based evaluator explained below):
+The ``^`` operator is often mistaken for a exponent operator, not the bitwise 
+operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you 
can
+replace the operator like this:
 
 .. code-block:: python
 
     >>> import ast
-    >>> import operator
+    >>> from simpleeval import safe_power
 
     >>> s = SimpleEval()
-    >>> s.operators[ast.BitXor] = operator.xor
+    >>> s.operators[ast.BitXor] = safe_power
 
-    >>> s.eval("2 ^ 10")
-    8
+    >>> s.eval("3 ^ 2")
+    9
+
+for example.
 
 Limited Power
 ~~~~~~~~~~~~~
@@ -287,6 +308,22 @@
 
     # and so on...
 
+One useful feature of using the ``SimpleEval`` object is that you can parse an 
expression
+once, and then evaluate it mulitple times using different ``names``:
+
+.. code-block:: python
+
+    # Set up & Cache the parse tree:
+    expression = "foo + bar"
+    parsed = s.parse(expression)
+
+    # evaluate the expression multiple times:
+    for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
+        s.names = names
+        print(s.eval(expression, previously_parsed=parsed))
+
+for instance.  This may help with performance.
+
 You can assign / edit the various options of the ``SimpleEval`` object if you
 want to.  Either assign them during creation (like the ``simple_eval``
 function)
@@ -400,9 +437,18 @@
 
 (requires ``entr``) 
 
+I'm trying to keep the codebase relatively clean with Black, isort, pylint & 
mypy.
+See::
+
+    $ make format
+
+and::
+
+    $ make lint
+
 BEWARE
 ------
 
-I've done the best I can with this library - but there's no warrenty, no 
guarentee, nada.  A lot of
+I've done the best I can with this library - but there's no warranty, no 
guarantee, nada.  A lot of
 very clever people think the whole idea of trying to sandbox CPython is 
impossible.  Read the code
 yourself, and use it at your own risk.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/pyproject.toml 
new/simpleeval-0.9.13/pyproject.toml
--- old/simpleeval-0.9.12/pyproject.toml        2022-01-15 17:27:18.000000000 
+0100
+++ new/simpleeval-0.9.13/pyproject.toml        2023-02-17 09:21:11.000000000 
+0100
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["setuptools>=30.3.0", "wheel", "build"]
+requires = ["setuptools>=30.3.0", "wheel"]
 build-backend = "setuptools.build_meta"
 
 [tool.black]
@@ -16,7 +16,9 @@
 
 [tool.pylint.messages_control]
 disable = [
+    "fixme",
     "consider-using-f-string",
+    "raise-missing-from",
     "invalid-name",
     "too-few-public-methods",
     "too-many-public-methods",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/setup.cfg 
new/simpleeval-0.9.13/setup.cfg
--- old/simpleeval-0.9.12/setup.cfg     2022-01-15 18:31:14.599745300 +0100
+++ new/simpleeval-0.9.13/setup.cfg     2023-02-17 09:24:18.892493500 +0100
@@ -1,6 +1,6 @@
 [metadata]
 name = simpleeval
-version = 0.9.12
+version = 0.9.13
 author = Daniel Fairhead
 author_email = [email protected]
 description = A simple, safe single expression evaluator library.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/simpleeval.egg-info/PKG-INFO 
new/simpleeval-0.9.13/simpleeval.egg-info/PKG-INFO
--- old/simpleeval-0.9.12/simpleeval.egg-info/PKG-INFO  2022-01-15 
18:31:14.000000000 +0100
+++ new/simpleeval-0.9.13/simpleeval.egg-info/PKG-INFO  2023-02-17 
09:24:18.000000000 +0100
@@ -1,13 +1,11 @@
 Metadata-Version: 2.1
 Name: simpleeval
-Version: 0.9.12
+Version: 0.9.13
 Summary: A simple, safe single expression evaluator library.
 Home-page: https://github.com/danthedeckie/simpleeval
 Author: Daniel Fairhead
 Author-email: [email protected]
-License: UNKNOWN
 Keywords: eval,simple,expression,parse,ast
-Platform: UNKNOWN
 Classifier: Development Status :: 4 - Beta
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
@@ -31,7 +29,14 @@
    :target: https://badge.fury.io/py/simpleeval
    :alt: PyPI Version
 
-A quick single file library for easily adding evaluatable expressions into
+.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
+   :target: https://github.com/psf/black
+
+.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
+   :target: https://github.com/PyCQA/pylint
+
+
+A single file library for easily adding evaluatable expressions into
 python projects.  Say you want to allow a user to set an alarm volume, which
 could depend on the time of day, alarm level, how many previous alarms had gone
 off, and if there is music playing at the time.
@@ -39,8 +44,9 @@
 Or if you want to allow simple formulae in a web application, but don't want to
 give full eval() access, or don't want to run in javascript on the client side.
 
-It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
-even just a single file you can dump into a project.
+It's deliberately trying to stay simple to use and not have millions of 
features,
+pull it in from PyPI (pip or easy_install), or even just a single file you can 
dump
+into a project.
 
 Internally, it's using the amazing python ``ast`` module to parse the
 expression, which allows very fine control of what is and isn't allowed.  It
@@ -55,7 +61,7 @@
 
 You should be aware of this when deploying in a public setting.
 
-The defaults are pretty locked down and basic, and it's very easy to add
+The defaults are pretty locked down and basic, and it's easy to add
 whatever extra specific functionality you need (your own functions,
 variable/name lookup, etc).
 
@@ -149,23 +155,36 @@
 |        | ``"spam" in "my breakfast"``       |
 |        | -> ``False``                       |
 +--------+------------------------------------+
+| ``^``  | "bitwise exclusive OR" (xor)       |
+|        | ``62 ^ 20`` -> ``42``              |
++--------+------------------------------------+
+| ``|``  | "bitwise OR"                       |
+|        | ``8 | 34`` -> ``42``               |
++--------+------------------------------------+
+| ``&``  | "bitwise AND"                      |
+|        | ``100 & 63`` -> ``36``             |
++--------+------------------------------------+
+| ``~``  | "bitwise invert"                   |
+|        | ``~ -43`` -> ``42``                |
++--------+------------------------------------+
 
 
-The ``^`` operator is notably missing - not because it's hard, but because it
-is often mistaken for a exponent operator, not the bitwise operation that it is
-in python.  It's trivial to add back in again if you wish (using the class
-based evaluator explained below):
+The ``^`` operator is often mistaken for a exponent operator, not the bitwise 
+operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you 
can
+replace the operator like this:
 
 .. code-block:: python
 
     >>> import ast
-    >>> import operator
+    >>> from simpleeval import safe_power
 
     >>> s = SimpleEval()
-    >>> s.operators[ast.BitXor] = operator.xor
+    >>> s.operators[ast.BitXor] = safe_power
+
+    >>> s.eval("3 ^ 2")
+    9
 
-    >>> s.eval("2 ^ 10")
-    8
+for example.
 
 Limited Power
 ~~~~~~~~~~~~~
@@ -305,6 +324,22 @@
 
     # and so on...
 
+One useful feature of using the ``SimpleEval`` object is that you can parse an 
expression
+once, and then evaluate it mulitple times using different ``names``:
+
+.. code-block:: python
+
+    # Set up & Cache the parse tree:
+    expression = "foo + bar"
+    parsed = s.parse(expression)
+
+    # evaluate the expression multiple times:
+    for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
+        s.names = names
+        print(s.eval(expression, previously_parsed=parsed))
+
+for instance.  This may help with performance.
+
 You can assign / edit the various options of the ``SimpleEval`` object if you
 want to.  Either assign them during creation (like the ``simple_eval``
 function)
@@ -418,11 +453,18 @@
 
 (requires ``entr``) 
 
+I'm trying to keep the codebase relatively clean with Black, isort, pylint & 
mypy.
+See::
+
+    $ make format
+
+and::
+
+    $ make lint
+
 BEWARE
 ------
 
-I've done the best I can with this library - but there's no warrenty, no 
guarentee, nada.  A lot of
+I've done the best I can with this library - but there's no warranty, no 
guarantee, nada.  A lot of
 very clever people think the whole idea of trying to sandbox CPython is 
impossible.  Read the code
 yourself, and use it at your own risk.
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/simpleeval.py 
new/simpleeval-0.9.13/simpleeval.py
--- old/simpleeval-0.9.12/simpleeval.py 2022-01-15 18:18:56.000000000 +0100
+++ new/simpleeval-0.9.13/simpleeval.py 2023-02-17 09:21:11.000000000 +0100
@@ -1,5 +1,5 @@
 """
-SimpleEval - (C) 2013-2022 Daniel Fairhead
+SimpleEval - (C) 2013-2023 Daniel Fairhead
 -------------------------------------
 
 An short, easy to use, safe and reasonably extensible expression evaluator.
@@ -49,12 +49,14 @@
 - mommothazaz123 (Andrew Zhu) f"string" support, Python 3.8 support
 - lubieowoce (Uryga) various potential vulnerabilities
 - JCavallo (Jean Cavallo) names dict shouldn't be modified
-- Birne94 (Daniel Birnstiel) for fixing leaking generators.
+- Birne94 (Daniel Birnstiel) for fixing leaking generators, star expressions
 - patricksurry (Patrick Surry) or should return last value, even if falsy.
 - shughes-uk (Samantha Hughes) python w/o 'site' should not fail to import.
-- KOLANICH packaging / deployment / setup help & << + >> ops
+- KOLANICH packaging / deployment / setup help & << + >> & other bit ops
 - graingert (Thomas Grainger) packaging / deployment / setup help
 - bozokopic (Bozo Kopic) Memory leak fix
+- daxamin (Dax Amin) Better error for attempting to eval empty string
+- smurfix (Matthias Urlichs) Allow clearing functions / operators / etc 
completely
 
 -------------------------------------
 Basic Usage:
@@ -101,6 +103,7 @@
 from random import random
 
 PYTHON3 = sys.version_info[0] == 3
+PYTHON35 = PYTHON3 and sys.version_info > (3, 5)
 
 ########################################
 # Module wide 'globals'
@@ -109,6 +112,7 @@
 MAX_COMPREHENSION_LENGTH = 10000
 MAX_POWER = 4000000  # highest exponent
 MAX_SHIFT = 10000  # highest << or >> (lshift / rshift)
+MAX_SHIFT_BASE = int(sys.float_info.max)  # highest on left side of << or >>
 DISALLOW_PREFIXES = ["_", "func_"]
 DISALLOW_METHODS = ["format", "format_map", "mro"]
 
@@ -121,7 +125,7 @@
 # builtins is a dict in python >3.6 but a module before
 DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, 
open}
 if hasattr(__builtins__, "help") or (
-    hasattr(__builtins__, "__contains__") and "help" in __builtins__
+    hasattr(__builtins__, "__contains__") and "help" in __builtins__  # type: 
ignore
 ):
     # PyInstaller environment doesn't include this module.
     DISALLOW_FUNCTIONS.add(help)
@@ -179,6 +183,17 @@
         super(InvalidExpression, self).__init__(self.message)
 
 
+class OperatorNotDefined(InvalidExpression):
+    """operator does not exist"""
+
+    def __init__(self, attr, expression):
+        self.message = "Operator '{0}' does not exist in expression 
'{1}'".format(attr, expression)
+        self.attr = attr
+        self.expression = expression
+
+        super(InvalidExpression, self).__init__(self.message)
+
+
 class FeatureNotAvailable(InvalidExpression):
     """What you're trying to do is not allowed."""
 
@@ -204,6 +219,12 @@
     pass
 
 
+class MultipleExpressions(UserWarning):
+    """Only the first expression parsed will be used"""
+
+    pass
+
+
 ########################################
 # Default simple functions to include:
 
@@ -219,7 +240,7 @@
 
     if abs(a) > MAX_POWER or abs(b) > MAX_POWER:
         raise NumberTooHigh("Sorry! I don't want to evaluate {0} ** 
{1}".format(a, b))
-    return a ** b
+    return a**b
 
 
 def safe_mult(a, b):  # pylint: disable=invalid-name
@@ -245,15 +266,15 @@
 
 
 def safe_rshift(a, b):  # pylint: disable=invalid-name
-    """rshift, but with the maximum"""
-    if abs(b) > MAX_SHIFT:
+    """rshift, but with input limits"""
+    if abs(b) > MAX_SHIFT or abs(a) > MAX_SHIFT_BASE:
         raise NumberTooHigh("Sorry! I don't want to evaluate {0} >> 
{1}".format(a, b))
     return a >> b
 
 
 def safe_lshift(a, b):  # pylint: disable=invalid-name
-    """lshift, but with the maximum"""
-    if abs(b) > MAX_SHIFT:
+    """lshift, but with input limits"""
+    if abs(b) > MAX_SHIFT or abs(a) > MAX_SHIFT_BASE:
         raise NumberTooHigh("Sorry! I don't want to evaluate {0} << 
{1}".format(a, b))
     return a << b
 
@@ -280,6 +301,10 @@
     ast.Not: op.not_,
     ast.USub: op.neg,
     ast.UAdd: op.pos,
+    ast.BitXor: op.xor,
+    ast.BitOr: op.or_,
+    ast.BitAnd: op.and_,
+    ast.Invert: op.invert,
     ast.In: lambda x, y: op.contains(y, x),
     ast.NotIn: lambda x, y: not op.contains(y, x),
     ast.Is: lambda x, y: x is y,
@@ -291,7 +316,8 @@
     "randint": random_int,
     "int": int,
     "float": float,
-    "str": str if PYTHON3 else unicode,
+    # pylint: disable=undefined-variable
+    "str": str if PYTHON3 else unicode,  # type: ignore
 }
 
 DEFAULT_NAMES = {"True": True, "False": False, "None": None}
@@ -317,11 +343,11 @@
         Create the evaluator instance.  Set up valid operators (+,-, etc)
         functions (add, random, get_val, whatever) and names."""
 
-        if not operators:
+        if operators is None:
             operators = DEFAULT_OPERATORS.copy()
-        if not functions:
+        if functions is None:
             functions = DEFAULT_FUNCTIONS.copy()
-        if not names:
+        if names is None:
             names = DEFAULT_NAMES.copy()
 
         self.operators = operators
@@ -377,16 +403,29 @@
     def __del__(self):
         self.nodes = None
 
-    def eval(self, expr):
+    @staticmethod
+    def parse(expr):
+        """parse an expression into a node tree"""
+
+        parsed = ast.parse(expr.strip())
+
+        if not parsed.body:
+            raise InvalidExpression("Sorry, cannot evaluate empty string")
+        if len(parsed.body) > 1:
+            warnings.warn(
+                "'{}' contains multiple expressions. Only the first will be 
used.".format(expr),
+                MultipleExpressions,
+            )
+        return parsed.body[0]
+
+    def eval(self, expr, previously_parsed=None):
         """evaluate an expresssion, using the operators, functions and
         names previously set up."""
 
         # set a copy of the expression aside, so we can give nice errors...
-
         self.expr = expr
 
-        # and evaluate:
-        return self._eval(ast.parse(expr.strip()).body[0])
+        return self._eval(previously_parsed or self.parse(expr))
 
     def _eval(self, node):
         """The internal evaluator used on each node in the parsed tree."""
@@ -415,7 +454,8 @@
         )
         return self._eval(node.value)
 
-    def _eval_import(self, node):
+    @staticmethod
+    def _eval_import(node):
         raise FeatureNotAvailable("Sorry, 'import' is not allowed.")
 
     @staticmethod
@@ -441,25 +481,32 @@
         return node.value
 
     def _eval_unaryop(self, node):
-        return self.operators[type(node.op)](self._eval(node.operand))
+        try:
+            operator = self.operators[type(node.op)]
+        except KeyError:
+            raise OperatorNotDefined(node.op, self.expr)
+        return operator(self._eval(node.operand))
 
     def _eval_binop(self, node):
-        return self.operators[type(node.op)](self._eval(node.left), 
self._eval(node.right))
+        try:
+            operator = self.operators[type(node.op)]
+        except KeyError:
+            raise OperatorNotDefined(node.op, self.expr)
+        return operator(self._eval(node.left), self._eval(node.right))
 
     def _eval_boolop(self, node):
+        to_return = False
         if isinstance(node.op, ast.And):
-            vout = False
             for value in node.values:
-                vout = self._eval(value)
-                if not vout:
-                    return vout
-            return vout
+                to_return = self._eval(value)
+                if not to_return:
+                    break
         elif isinstance(node.op, ast.Or):
             for value in node.values:
-                vout = self._eval(value)
-                if vout:
-                    return vout
-            return vout
+                to_return = self._eval(value)
+                if to_return:
+                    break
+        return to_return
 
     def _eval_compare(self, node):
         right = self._eval(node.left)
@@ -483,7 +530,7 @@
                 func = self.functions[node.func.id]
             except KeyError:
                 raise FunctionNotDefined(node.func.id, self.expr)
-            except AttributeError as e:
+            except AttributeError:
                 raise FeatureNotAvailable("Lambda Functions not implemented")
 
             if func in DISALLOW_FUNCTIONS:
@@ -505,14 +552,13 @@
             # pass that to ast.parse)
             if hasattr(self.names, "__getitem__"):
                 return self.names[node.id]
-            elif callable(self.names):
+            if callable(self.names):
                 return self.names(node)
-            else:
-                raise InvalidExpression(
-                    'Trying to use name (variable) "{0}"'
-                    ' when no "names" defined for'
-                    " evaluator".format(node.id)
-                )
+            raise InvalidExpression(
+                'Trying to use name (variable) "{0}"'
+                ' when no "names" defined for'
+                " evaluator".format(node.id)
+            )
 
         except KeyError:
             if node.id in self.functions:
@@ -523,10 +569,9 @@
     def _eval_subscript(self, node):
         container = self._eval(node.value)
         key = self._eval(node.slice)
-        try:
-            return container[key]
-        except KeyError:
-            raise
+        # Currently if there's a KeyError, that gets raised straight up.
+        # TODO: Should that be wrapped in an InvalidExpression?
+        return container[key]
 
     def _eval_attribute(self, node):
         for prefix in DISALLOW_PREFIXES:
@@ -595,6 +640,8 @@
     function editions. (list, tuple, dict, set).
     """
 
+    _max_count = 0
+
     def __init__(self, operators=None, functions=None, names=None):
         super(EvalWithCompoundTypes, self).__init__(operators, functions, 
names)
 
@@ -611,18 +658,36 @@
             }
         )
 
-    def eval(self, expr):
+    def eval(self, expr, previously_parsed=None):
+        # reset _max_count for each eval run
         self._max_count = 0
-        return super(EvalWithCompoundTypes, self).eval(expr)
+        return super(EvalWithCompoundTypes, self).eval(expr, previously_parsed)
 
     def _eval_dict(self, node):
-        return {self._eval(k): self._eval(v) for (k, v) in zip(node.keys, 
node.values)}
+        result = {}
 
-    def _eval_tuple(self, node):
-        return tuple(self._eval(x) for x in node.elts)
+        for key, value in zip(node.keys, node.values):
+            if PYTHON35 and key is None:
+                # "{**x}" gets parsed as a key-value pair of (None, Name(x))
+                result.update(self._eval(value))
+            else:
+                result[self._eval(key)] = self._eval(value)
+
+        return result
 
     def _eval_list(self, node):
-        return list(self._eval(x) for x in node.elts)
+        result = []
+
+        for item in node.elts:
+            if PYTHON3 and isinstance(item, ast.Starred):
+                result.extend(self._eval(item.value))
+            else:
+                result.append(self._eval(item))
+
+        return result
+
+    def _eval_tuple(self, node):
+        return tuple(self._eval(x) for x in node.elts)
 
     def _eval_set(self, node):
         return set(self._eval(x) for x in node.elts)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/simpleeval-0.9.12/test_simpleeval.py 
new/simpleeval-0.9.13/test_simpleeval.py
--- old/simpleeval-0.9.12/test_simpleeval.py    2022-01-15 18:22:19.000000000 
+0100
+++ new/simpleeval-0.9.13/test_simpleeval.py    2023-02-17 09:21:11.000000000 
+0100
@@ -1,4 +1,4 @@
-# pylint: disable=too-many-public-methods, missing-docstring, too-many-lines, 
use-of-eval, disallowed-variable, no-self-use
+# pylint: disable=too-many-public-methods, missing-docstring, eval-used, 
too-many-lines, no-self-use, disallowed-name, unspecified-encoding
 
 """
     Unit tests for simpleeval.
@@ -24,6 +24,7 @@
     FunctionNotDefined,
     InvalidExpression,
     NameNotDefined,
+    OperatorNotDefined,
     SimpleEval,
     simple_eval,
 )
@@ -82,6 +83,13 @@
             -10,
         )
 
+    def test_bit_ops(self):
+        self.t("62 ^ 20", 42)
+        self.t("62 ^ 100", 90)
+        self.t("8 | 34", 42)
+        self.t("100 & 63", 36)
+        self.t("~ -43", 42)
+
     def test_not(self):
         self.t("not False", True)
         self.t("not True", False)
@@ -109,6 +117,7 @@
         self.t("1 < 2 < 3 < 4", 1 < 2 < 3 < 4)
         self.t("1 < 2 > 3 < 4", 1 < 2 > 3 < 4)
 
+        # pylint: disable=comparison-with-itself
         self.t("1<2<1+1", 1 < 2 < 1 + 1)
         self.t("1 == 1 == 2", 1 == 1 == 2)
         self.t("1 == 1 < 2", 1 == 1 < 2)
@@ -189,6 +198,49 @@
         with self.assertRaises(FeatureNotAvailable):
             self.t("{22}", False)
 
+    def test_empty_string_not_allowed(self):
+        with self.assertRaises(InvalidExpression):
+            self.t("", False)
+
+
+class TestEvaluator(DRYTest):
+    """Tests for how the SimpleEval class does things"""
+
+    def test_only_evalutate_first_statement(self):
+        # it only evaluates the first statement:
+        with warnings.catch_warnings(record=True) as ws:
+            self.t("11; x = 21; x + x", 11)
+        self.assertIsInstance(ws[0].message, simpleeval.MultipleExpressions)
+
+    def test_parse_and_use_previously_parsed(self):
+        expr = "x + x"
+        nodes = self.s.parse(expr)
+        self.s.names = {"x": 21}
+        self.assertEqual(self.s.eval(expr, nodes), 42)
+
+        # This can all be done with unittest.mock.patch in python3.3+ - when 
we drop
+        # python2 - we can drop this nonsense.
+        class MockedCalled(Exception):
+            pass
+
+        def go_boom(*args, **kwargs):
+            raise MockedCalled("you should never see this.")
+
+        self.s.parse = go_boom
+
+        # Prove the mock is installed in self.s
+        with self.assertRaises(MockedCalled):
+            self.s.eval("10 + 10")
+
+        # Prove it's not installed in the actual SimpleEval
+        SimpleEval().eval("10 + 10")
+
+        # Now running .eval with a previously parsed
+        self.assertEqual(self.s.eval(expr, previously_parsed=nodes), 42)
+
+        self.s.names = {"x": 100}
+        self.assertEqual(self.s.eval(expr, nodes), 200)
+
 
 class TestFunctions(DRYTest):
     """Functions for expressions to play with"""
@@ -258,29 +310,29 @@
         self.t("foo()", 42)
 
     def test_function_args_required(self):
-        def foo(toret):
-            return toret
+        def foo(to_return):
+            return to_return
 
         self.s.functions["foo"] = foo
         with self.assertRaises(TypeError):
             self.t("foo()", 42)
 
         self.t("foo(12)", 12)
-        self.t("foo(toret=100)", 100)
+        self.t("foo(to_return=100)", 100)
 
     def test_function_args_defaults(self):
-        def foo(toret=9999):
-            return toret
+        def foo(to_return=9999):
+            return to_return
 
         self.s.functions["foo"] = foo
         self.t("foo()", 9999)
 
         self.t("foo(12)", 12)
-        self.t("foo(toret=100)", 100)
+        self.t("foo(to_return=100)", 100)
 
     def test_function_args_bothtypes(self):
-        def foo(mult, toret=100):
-            return toret * mult
+        def foo(mult, to_return=100):
+            return to_return * mult
 
         self.s.functions["foo"] = foo
         with self.assertRaises(TypeError):
@@ -289,10 +341,10 @@
         self.t("foo(2)", 200)
 
         with self.assertRaises(TypeError):
-            self.t("foo(toret=100)", 100)
+            self.t("foo(to_return=100)", 100)
 
-        self.t("foo(4, toret=4)", 16)
-        self.t("foo(mult=2, toret=4)", 8)
+        self.t("foo(4, to_return=4)", 16)
+        self.t("foo(mult=2, to_return=4)", 8)
         self.t("foo(2, 10)", 20)
 
 
@@ -328,7 +380,7 @@
         """exponent operations can take a long time."""
         old_max = simpleeval.MAX_POWER
 
-        self.t("9**9**5", 9 ** 9 ** 5)
+        self.t("9**9**5", 9**9**5)
 
         with self.assertRaises(simpleeval.NumberTooHigh):
             self.t("9**9**8", 0)
@@ -350,8 +402,14 @@
             self.t("1<<25000", 0)
 
         with self.assertRaises(simpleeval.NumberTooHigh):
+            self.t("%s<<25" % (simpleeval.MAX_SHIFT_BASE + 1), 0)
+
+        with self.assertRaises(simpleeval.NumberTooHigh):
             self.t("1>>25000", 0)
 
+        with self.assertRaises(simpleeval.NumberTooHigh):
+            self.t("%s>>25" % (simpleeval.MAX_SHIFT_BASE + 1), 0)
+
         # and test we can change it:
 
         old_max = simpleeval.MAX_SHIFT
@@ -420,11 +478,6 @@
         with self.assertRaises(simpleeval.IterableTooLong):
             self.t("('spam spam spam' * 5000).split() * 5000", None)
 
-    def test_python_stuff(self):
-        """other various pythony things."""
-        # it only evaluates the first statement:
-        self.t("11; x = 21; x + x", 11)
-
     def test_function_globals_breakout(self):
         """by accessing function.__globals__ or func_..."""
         # thanks perkinslr.
@@ -559,6 +612,24 @@
         self.t('{"a": 24}.get("b", 11)', 11)
         self.t('"a" in {"a": 24}', True)
 
+    @unittest.skipIf(not simpleeval.PYTHON35, "feature not supported")
+    def test_dict_star_expression(self):
+        self.s.names["x"] = {"a": 1, "b": 2}
+        self.t('{"a": 0, **x, "c": 3}', {"a": 1, "b": 2, "c": 3})
+
+        # and multiple star expressions should be fine too...
+        self.s.names["y"] = {"x": 1, "y": 2}
+        self.t('{"a": 0, **x, **y, "c": 3}', {"a": 1, "b": 2, "c": 3, "x": 1, 
"y": 2})
+
+    @unittest.skipIf(not simpleeval.PYTHON35, "feature not supported")
+    def test_dict_invalid_star_expression(self):
+        self.s.names["x"] = {"a": 1, "b": 2}
+        self.s.names["y"] = {"x": 1, "y": 2}
+        self.s.names["z"] = 42
+
+        with self.assertRaises(TypeError):
+            self.t('{"a": 0, **x, **y, **z, "c": 3}', {"a": 1, "b": 2, "c": 3})
+
     def test_tuple(self):
         self.t("()", ())
         self.t("(1,)", (1,))
@@ -589,6 +660,19 @@
 
         self.t('"b" in ["a","b"]', True)
 
+    @unittest.skipIf(not simpleeval.PYTHON3, "feature not supported")
+    def test_list_star_expression(self):
+        self.s.names["x"] = [1, 2, 3]
+        self.t('["a", *x, "b"]', ["a", 1, 2, 3, "b"])
+
+    @unittest.skipIf(not simpleeval.PYTHON3, "feature not supported")
+    def test_list_invalid_star_expression(self):
+        self.s.names["x"] = [1, 2, 3]
+        self.s.names["y"] = 42
+
+        with self.assertRaises(TypeError):
+            self.t('["a", *x, *y, "b"]', ["a", 1, 2, 3, "b"])
+
     def test_set(self):
         self.t("{1}", {1})
         self.t("{1, 2, 1, 2, 1, 2, 1}", {1, 2})
@@ -708,9 +792,11 @@
 
         self.s.names["s"] = 21
 
+        # or if you attempt to assign an unknown name to another
         with self.assertRaises(NameNotDefined):
             with warnings.catch_warnings(record=True) as ws:
                 self.t("s += a", 21)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.s.names = None
 
@@ -735,6 +821,7 @@
         # however, you can't assign to those names:
         with warnings.catch_warnings(record=True) as ws:
             self.t("a = 200", 200)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.assertEqual(self.s.names["a"], 42)
 
@@ -744,6 +831,7 @@
 
         with warnings.catch_warnings(record=True) as ws:
             self.t("b[0] = 11", 11)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.assertEqual(self.s.names["b"], [0])
 
@@ -766,6 +854,7 @@
 
         with warnings.catch_warnings(record=True) as ws:
             self.t("c['b'] = 99", 99)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.assertFalse("b" in self.s.names["c"])
 
@@ -775,6 +864,7 @@
 
         with warnings.catch_warnings(record=True) as ws:
             self.t("c['c']['c'] = 21", 21)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.assertEqual(self.s.names["c"]["c"]["c"], 11)
 
@@ -789,6 +879,7 @@
 
         with warnings.catch_warnings(record=True) as ws:
             self.t("a.b.c = 11", 11)
+        self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
 
         self.assertEqual(self.s.names["a"]["b"]["c"], 42)
 
@@ -816,6 +907,7 @@
 
     def test_object(self):
         """using an object for name lookup"""
+        # pylint: disable=attribute-defined-outside-init
 
         class TestObject(object):
             @staticmethod
@@ -1032,10 +1124,12 @@
                 return BinaryExpression("LT")
 
         b = Blah()
-        self.s.names = {"b": b}
-        # This should not crash:
-        e = eval("b > 2", self.s.names)
+        # These should not crash:
+        self.assertEqual(b > 2, BinaryExpression("GT"))
+        self.assertEqual(b < 2, BinaryExpression("LT"))
 
+        # And should also work in simpleeval
+        self.s.names = {"b": b}
         self.t("b > 2", BinaryExpression("GT"))
         self.t("1 < 5 > b", BinaryExpression("LT"))
 
@@ -1056,7 +1150,7 @@
 
         self.assertEqual(m.anything, 42)
         with self.assertRaises(NotImplementedError):
-            m["nothing"]
+            m["nothing"]  # pylint: disable=pointless-statement
 
         self.s.names = {"m": m}
         self.t("m.anything", 42)
@@ -1108,6 +1202,7 @@
     def test_functions_are_disallowed_at_init(self):
         DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, 
compile, open]
         if simpleeval.PYTHON3:
+            # pylint: disable=exec-used
             exec("DISALLOWED.append(exec)")  # exec is not a function in 
Python2...
 
         for f in simpleeval.DISALLOW_FUNCTIONS:
@@ -1115,12 +1210,13 @@
 
         for x in DISALLOWED:
             with self.assertRaises(FeatureNotAvailable):
-                s = SimpleEval(functions={"foo": x})
+                SimpleEval(functions={"foo": x})
 
     def test_functions_are_disallowed_in_expressions(self):
         DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, 
compile, open]
 
         if simpleeval.PYTHON3:
+            # pylint: disable=exec-used
             exec("DISALLOWED.append(exec)")  # exec is not a function in 
Python2...
 
         for f in simpleeval.DISALLOW_FUNCTIONS:
@@ -1138,11 +1234,13 @@
         simpleeval.DEFAULT_FUNCTIONS = DF.copy()
 
 
[email protected](simpleeval.PYTHON3 != True, "Python2 fails - but it's not 
supported anyway.")
[email protected](simpleeval.PYTHON3 is not True, "Python2 fails - but it's not 
supported anyway.")
 @unittest.skipIf(platform.python_implementation() == "PyPy", "GC set_debug not 
available in PyPy")
 class TestReferenceCleanup(DRYTest):
     """Test cleanup without cyclic references"""
 
+    # pylint: disable=attribute-defined-outside-init
+
     def setUp(self):
         self._initial_gc_isenabled = gc.isenabled()
 
@@ -1165,5 +1263,35 @@
         simpleeval.SimpleEval()
 
 
+class TestNoEntries(DRYTest):
+    def test_no_functions(self):
+        self.s.eval("int(42)")
+        with self.assertRaises(FunctionNotDefined):
+            s = SimpleEval(functions={})
+            s.eval("int(42)")
+
+    def test_no_names(self):
+        # does not work on current Py3, True et al. are keywords now
+        self.s.eval("True")
+        # with self.assertRaises(NameNotDefined):
+        s = SimpleEval(names={})
+        if sys.version_info < (3,):
+            with self.assertRaises(NameNotDefined):
+                s.eval("True")
+        else:
+            s.eval("True")
+
+    def test_no_operators(self):
+        self.s.eval("1+2")
+        self.s.eval("~2")
+        s = SimpleEval(operators={})
+
+        with self.assertRaises(OperatorNotDefined):
+            s.eval("1+2")
+
+        with self.assertRaises(OperatorNotDefined):
+            s.eval("~ 2")
+
+
 if __name__ == "__main__":  # pragma: no cover
     unittest.main()

Reply via email to