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 2026-03-24 18:48:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-simpleeval (Old)
and /work/SRC/openSUSE:Factory/.python-simpleeval.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-simpleeval"
Tue Mar 24 18:48:57 2026 rev:12 rq:1342124 version:1.0.7
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-simpleeval/python-simpleeval.changes
2026-03-16 14:21:17.659166407 +0100
+++
/work/SRC/openSUSE:Factory/.python-simpleeval.new.8177/python-simpleeval.changes
2026-03-24 18:49:51.832919287 +0100
@@ -1,0 +2,10 @@
+Mon Mar 23 22:55:22 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.0.7:
+ * Performance fixes for problems introduced by security fixes
+ in 1.0.5 / 1.0.6
+- update to 1.0.6:
+ * unable to pass unhashable items as kwargs introduced by
+ security fixes in 1.0.5 this morning.
+
+-------------------------------------------------------------------
Old:
----
simpleeval-1.0.5.tar.gz
New:
----
simpleeval-1.0.7.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-simpleeval.spec ++++++
--- /var/tmp/diff_new_pack.LHolQB/_old 2026-03-24 18:49:52.324939588 +0100
+++ /var/tmp/diff_new_pack.LHolQB/_new 2026-03-24 18:49:52.324939588 +0100
@@ -20,7 +20,7 @@
%define modname simpleeval
%{?sle15_python_module_pythons}
Name: python-%{modname}
-Version: 1.0.5
+Version: 1.0.7
Release: 0
Summary: A simple, safe single expression evaluator library
License: MIT
++++++ simpleeval-1.0.5.tar.gz -> simpleeval-1.0.7.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst
---
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst
2020-02-02 01:00:00.000000000 +0100
+++
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/docutils/parsers/rst/include/README.rst
1970-01-01 01:00:00.000000000 +0100
@@ -1,17 +0,0 @@
-============================================
- ``docutils/parsers/rst/include`` Directory
-============================================
-
-This directory contains standard data files intended for inclusion in
-reStructuredText documents. To access these files, use the "include"
-directive with the special syntax for standard "include" data files,
-angle brackets around the file name::
-
- .. include:: <isonum.txt>
-
-See the documentation for the `"include" directive`__ and
-`reStructuredText Standard Definition Files`__ for
-details.
-
-__ https://docutils.sourceforge.io/docs/ref/rst/directives.html#include
-__ https://docutils.sourceforge.io/docs/ref/rst/definitions.html
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst
---
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst
2020-02-02 01:00:00.000000000 +0100
+++
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/docutils/writers/s5_html/themes/README.rst
1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +0,0 @@
-Except where otherwise noted, all files in this
-directory have been released into the Public Domain.
-
-These files are based on files from S5 1.1, released into the Public
-Domain by Eric Meyer. For further details, please see
-http://www.meyerweb.com/eric/tools/s5/credits.html.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
---
old/simpleeval-1.0.5/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
2020-02-02 01:00:00.000000000 +0100
+++
new/simpleeval-1.0.7/.v2/lib/python3.13/site-packages/pip/_vendor/README.rst
1970-01-01 01:00:00.000000000 +0100
@@ -1,180 +0,0 @@
-================
-Vendoring Policy
-================
-
-* Vendored libraries **MUST** not be modified except as required to
- successfully vendor them.
-* Vendored libraries **MUST** be released copies of libraries available on
- PyPI.
-* Vendored libraries **MUST** be available under a license that allows
- them to be integrated into ``pip``, which is released under the MIT license.
-* Vendored libraries **MUST** be accompanied with LICENSE files.
-* The versions of libraries vendored in pip **MUST** be reflected in
- ``pip/_vendor/vendor.txt``.
-* Vendored libraries **MUST** function without any build steps such as ``2to3``
- or compilation of C code, practically this limits to single source 2.x/3.x
and
- pure Python.
-* Any modifications made to libraries **MUST** be noted in
- ``pip/_vendor/README.rst`` and their corresponding patches **MUST** be
- included ``tools/vendoring/patches``.
-* Vendored libraries should have corresponding ``vendored()`` entries in
- ``pip/_vendor/__init__.py``.
-
-Rationale
-=========
-
-Historically pip has not had any dependencies except for ``setuptools`` itself,
-choosing instead to implement any functionality it needed to prevent needing
-a dependency. However, starting with pip 1.5, we began to replace code that was
-implemented inside of pip with reusable libraries from PyPI. This brought the
-typical benefits of reusing libraries instead of reinventing the wheel like
-higher quality and more battle tested code, centralization of bug fixes
-(particularly security sensitive ones), and better/more features for less work.
-
-However, there are several issues with having dependencies in the traditional
-way (via ``install_requires``) for pip. These issues are:
-
-**Fragility**
- When pip depends on another library to function then if for whatever reason
- that library either isn't installed or an incompatible version is installed
- then pip ceases to function. This is of course true for all Python
- applications, however for every application *except* for pip the way you fix
- it is by re-running pip. Obviously, when pip can't run, you can't use pip to
- fix pip, so you're left having to manually resolve dependencies and
- installing them by hand.
-
-**Making other libraries uninstallable**
- One of pip's current dependencies is the ``requests`` library, for which pip
- requires a fairly recent version to run. If pip depended on ``requests`` in
- the traditional manner, then we'd either have to maintain compatibility with
- every ``requests`` version that has ever existed (and ever will), OR allow
- pip to render certain versions of ``requests`` uninstallable. (The second
- issue, although technically true for any Python application, is magnified by
- pip's ubiquity; pip is installed by default in Python, in ``pyvenv``, and in
- ``virtualenv``.)
-
-**Security**
- This might seem puzzling at first glance, since vendoring has a tendency to
- complicate updating dependencies for security updates, and that holds true
- for pip. However, given the *other* reasons for avoiding dependencies, the
- alternative is for pip to reinvent the wheel itself. This is what pip did
- historically. It forced pip to re-implement its own HTTPS verification
- routines as a workaround for the Python standard library's lack of SSL
- validation, which resulted in similar bugs in the validation routine in
- ``requests`` and ``urllib3``, except that they had to be discovered and
- fixed independently. Even though we're vendoring, reusing libraries keeps
- pip more secure by relying on the great work of our dependencies, *and*
- allowing for faster, easier security fixes by simply pulling in newer
- versions of dependencies.
-
-**Bootstrapping**
- Currently most popular methods of installing pip rely on pip's
- self-contained nature to install pip itself. These tools work by bundling a
- copy of pip, adding it to ``sys.path``, and then executing that copy of pip.
- This is done instead of implementing a "mini installer" (to reduce
- duplication); pip already knows how to install a Python package, and is far
- more battle-tested than any "mini installer" could ever possibly be.
-
-Many downstream redistributors have policies against this kind of bundling, and
-instead opt to patch the software they distribute to debundle it and make it
-rely on the global versions of the software that they already have packaged
-(which may have its own patches applied to it). We (the pip team) would prefer
-it if pip was *not* debundled in this manner due to the above reasons and
-instead we would prefer it if pip would be left intact as it is now.
-
-In the longer term, if someone has a *portable* solution to the above problems,
-other than the bundling method we currently use, that doesn't add additional
-problems that are unreasonable then we would be happy to consider, and possibly
-switch to said method. This solution must function correctly across all of the
-situation that we expect pip to be used and not mandate some external mechanism
-such as OS packages.
-
-
-Modifications
-=============
-
-* ``setuptools`` is completely stripped to only keep ``pkg_resources``.
-* ``pkg_resources`` has been modified to import its dependencies from
- ``pip._vendor``, and to use the vendored copy of ``platformdirs``
- rather than ``appdirs``.
-* ``packaging`` has been modified to import its dependencies from
- ``pip._vendor``.
-* ``CacheControl`` has been modified to import its dependencies from
- ``pip._vendor``.
-* ``requests`` has been modified to import its other dependencies from
- ``pip._vendor`` and to *not* load ``simplejson`` (all platforms) and
- ``pyopenssl`` (Windows).
-* ``platformdirs`` has been modified to import its submodules from
``pip._vendor.platformdirs``.
-
-Automatic Vendoring
-===================
-
-Vendoring is automated via the `vendoring
<https://pypi.org/project/vendoring/>`_ tool from the content of
-``pip/_vendor/vendor.txt`` and the different patches in
-``tools/vendoring/patches``.
-Launch it via ``vendoring sync . -v`` (requires ``vendoring>=0.2.2``).
-Tool configuration is done via ``pyproject.toml``.
-
-To update the vendored library versions, we have a session defined in ``nox``.
-The command to upgrade everything is::
-
- nox -s vendoring -- --upgrade-all --skip urllib3 --skip setuptools
-
-At the time of writing (April 2025) we do not upgrade ``urllib3`` because the
-next version is a major upgrade and will be handled as an independent PR. We
also
-do not upgrade ``setuptools``, because we only rely on ``pkg_resources``, and
-tracking every ``setuptools`` change is unnecessary for our needs.
-
-
-Managing Local Patches
-======================
-
-The ``vendoring`` tool automatically applies our local patches, but updating,
-the patches sometimes no longer apply cleanly. In that case, the update will
-fail. To resolve this, take the following steps:
-
-1. Revert any incomplete changes in the revendoring branch, to ensure you have
- a clean starting point.
-2. Run the revendoring of the library with a problem again: ``nox -s vendoring
- -- --upgrade <library_name>``.
-3. This will fail again, but you will have the original source in your working
- directory. Review the existing patch against the source, and modify the
patch
- to reflect the new version of the source. If you ``git add`` the changes the
- vendoring made, you can modify the source to reflect the patch file and then
- generate a new patch with ``git diff``.
-4. Now, revert everything *except* the patch file changes. Leave the modified
- patch file unstaged but saved in the working tree.
-5. Re-run the vendoring. This time, it should pick up the changed patch file
- and apply it cleanly. The patch file changes will be committed along with
the
- revendoring, so the new commit should be ready to test and publish as a PR.
-
-
-Debundling
-==========
-
-As mentioned in the rationale, we, the pip team, would prefer it if pip was not
-debundled (other than optionally ``pip/_vendor/requests/cacert.pem``) and that
-pip was left intact. However, if you insist on doing so, we have a
-semi-supported method (that we don't test in our CI) and requires a bit of
-extra work on your end in order to solve the problems described above.
-
-1. Delete everything in ``pip/_vendor/`` **except** for
- ``pip/_vendor/__init__.py`` and ``pip/_vendor/vendor.txt``.
-2. Generate wheels for each of pip's dependencies (and any of their
- dependencies) using your patched copies of these libraries. These must be
- placed somewhere on the filesystem that pip can access (``pip/_vendor`` is
- the default assumption).
-3. Modify ``pip/_vendor/__init__.py`` so that the ``DEBUNDLED`` variable is
- ``True``.
-4. Upon installation, the ``INSTALLER`` file in pip's own ``dist-info``
- directory should be set to something other than ``pip``, so that pip
- can detect that it wasn't installed using itself.
-5. *(optional)* If you've placed the wheels in a location other than
- ``pip/_vendor/``, then modify ``pip/_vendor/__init__.py`` so that the
- ``WHEEL_DIR`` variable points to the location you've placed them.
-6. *(optional)* Update the ``pip_self_version_check`` logic to use the
- appropriate logic for determining the latest available version of pip and
- prompt the user with the correct upgrade message.
-
-Note that partial debundling is **NOT** supported. You need to prepare wheels
-for all dependencies for successful debundling.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/simpleeval-1.0.5/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE
new/simpleeval-1.0.7/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE
---
old/simpleeval-1.0.5/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE
2020-02-02 01:00:00.000000000 +0100
+++
new/simpleeval-1.0.7/.v314/lib/python3.14/site-packages/simpleeval-1.0.3.dist-info/licenses/LICENCE
1970-01-01 01:00:00.000000000 +0100
@@ -1,21 +0,0 @@
-simpleeval - Copyright (c) 2013-2024 Daniel Fairhead
-
-(MIT Licence)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/simpleeval-1.0.5/.v314/lib/python3.14/site-packages/simpleeval.py
new/simpleeval-1.0.7/.v314/lib/python3.14/site-packages/simpleeval.py
--- old/simpleeval-1.0.5/.v314/lib/python3.14/site-packages/simpleeval.py
2020-02-02 01:00:00.000000000 +0100
+++ new/simpleeval-1.0.7/.v314/lib/python3.14/site-packages/simpleeval.py
1970-01-01 01:00:00.000000000 +0100
@@ -1,768 +0,0 @@
-"""
-SimpleEval - (C) 2013-2024 Daniel Fairhead
--------------------------------------
-
-An short, easy to use, safe and reasonably extensible expression evaluator.
-Designed for things like in a website where you want to allow the user to
-generate a string, or a number from some other input, without allowing full
-eval() or other unsafe or needlessly complex linguistics.
-
--------------------------------------
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
--------------------------------------
-
-Initial idea copied from J.F. Sebastian on Stack Overflow
-( http://stackoverflow.com/a/9558001/1973500 ) with
-modifications and many improvements.
-
--------------------------------------
-Contributors:
-- corro (Robin Baumgartner) (py3k)
-- dratchkov (David R) (nested dicts)
-- marky1991 (Mark Young) (slicing)
-- T045T (Nils Berg) (!=, py3kstr, obj.
-- perkinslr (Logan Perkins) (.__globals__ or .func_ breakouts)
-- impala2 (Kirill Stepanov) (massive _eval refactor)
-- gk (ugik) (Other iterables than str can DOS too, and can be made)
-- daveisfera (Dave Johansen) 'not' Boolean op, Pycharm, pep8, various other
fixes
-- xaled (Khalid Grandi) method chaining correctly, double-eval bugfix.
-- EdwardBetts (Edward Betts) spelling correction.
-- charlax (Charles-Axel Dein charlax) Makefile and cleanups
-- 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, 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 & << + >> & 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
-- koenigsley (Mikhail Yeremeyev) documentation typos correction.
-- kurtmckee (Kurt McKee) Infrastructure updates
-- edgarrmondragon (Edgar Ramírez-Mondragón) Address Python 3.12+ deprecation
warnings
-- cedk (Cédric Krier) <[email protected]> Allow running tests with Werror
-- decorator-factory <[email protected]> More security fixes
-- lkruitwagen (Lucas Kruitwagen) Adding support for dict comprehensions
-
--------------------------------------
-Basic Usage:
-
->>> s = SimpleEval()
->>> s.eval("20 + 30")
-50
-
-You can add your own functions easily too:
-
-if file.txt contents is "11"
-
->>> def get_file():
-... with open("file.txt", 'r') as f:
-... return f.read()
-
->>> s.functions["get_file"] = get_file
->>> s.eval("int(get_file()) + 31")
-42
-
-For more information, see the full package documentation on pypi, or the github
-repo.
-
------------
-
-If you don't need to re-use the evaluator (with it's names, functions, etc),
-then you can use the simple_eval() function:
-
->>> simple_eval("21 + 19")
-40
-
-You can pass names, operators and functions to the simple_eval function as
-well:
-
->>> simple_eval("40 + two", names={"two": 2})
-42
-
-"""
-
-import ast
-import operator as op
-import sys
-import warnings
-from random import random
-
-########################################
-# Module wide 'globals'
-
-MAX_STRING_LENGTH = 100000
-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",
- "tb_frame",
- "gi_frame",
- "ag_frame",
- "cr_frame",
- "exec",
-]
-
-# Disallow functions:
-# This, strictly speaking, is not necessary. These /should/ never be
accessible anyway,
-# if DISALLOW_PREFIXES and DISALLOW_METHODS are all right. This is here to
try and help
-# people not be stupid. Allowing these functions opens up all sorts of holes
- if any of
-# their functionality is required, then please wrap them up in a safe
container. And think
-# very hard about it first. And don't say I didn't warn you.
-# builtins is a dict in python >3.6 but a module before
-DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile,
open, exec}
-if hasattr(__builtins__, "help") or (
- hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type:
ignore
-):
- # PyInstaller environment doesn't include this module.
- DISALLOW_FUNCTIONS.add(help)
-
-
-########################################
-# Exceptions:
-
-
-class InvalidExpression(Exception):
- """Generic Exception"""
-
- pass
-
-
-class FunctionNotDefined(InvalidExpression):
- """sorry! That function isn't defined!"""
-
- def __init__(self, func_name, expression):
- self.message = "Function '{0}' not defined," " for expression
'{1}'.".format(
- func_name, expression
- )
- setattr(self, "func_name", func_name) # bypass 2to3 confusion.
- self.expression = expression
-
- super(InvalidExpression, self).__init__(self.message)
-
-
-class NameNotDefined(InvalidExpression):
- """a name isn't defined."""
-
- def __init__(self, name, expression):
- self.name = name
- self.message = "'{0}' is not defined for expression
'{1}'".format(name, expression)
- self.expression = expression
-
- super(InvalidExpression, self).__init__(self.message)
-
-
-class AttributeDoesNotExist(InvalidExpression):
- """attribute does not exist"""
-
- def __init__(self, attr, expression):
- self.message = "Attribute '{0}' does not exist in expression
'{1}'".format(
- attr, expression
- )
- self.attr = attr
- self.expression = expression
-
- 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."""
-
- pass
-
-
-class NumberTooHigh(InvalidExpression):
- """Sorry! That number is too high. I don't want to spend the
- next 10 years evaluating this expression!"""
-
- pass
-
-
-class IterableTooLong(InvalidExpression):
- """That iterable is **way** too long, baby."""
-
- pass
-
-
-class AssignmentAttempted(UserWarning):
- """Assignment not allowed in SimpleEval"""
-
- pass
-
-
-class MultipleExpressions(UserWarning):
- """Only the first expression parsed will be used"""
-
- pass
-
-
-########################################
-# Default simple functions to include:
-
-
-def random_int(top):
- """return a random int below <top>"""
-
- return int(random() * top)
-
-
-def safe_power(a, b): # pylint: disable=invalid-name
- """a limited exponent/to-the-power-of function, for safety reasons"""
-
- 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
-
-
-def safe_mult(a, b): # pylint: disable=invalid-name
- """limit the number of times an iterable can be repeated..."""
-
- if hasattr(a, "__len__") and b * len(a) > MAX_STRING_LENGTH:
- raise IterableTooLong("Sorry, I will not evaluate something that
long.")
- if hasattr(b, "__len__") and a * len(b) > MAX_STRING_LENGTH:
- raise IterableTooLong("Sorry, I will not evaluate something that
long.")
-
- return a * b
-
-
-def safe_add(a, b): # pylint: disable=invalid-name
- """iterable length limit again"""
-
- if hasattr(a, "__len__") and hasattr(b, "__len__"):
- if len(a) + len(b) > MAX_STRING_LENGTH:
- raise IterableTooLong(
- "Sorry, adding those two together would" " make something too
long."
- )
- return a + b
-
-
-def safe_rshift(a, b): # pylint: disable=invalid-name
- """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 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
-
-
-########################################
-# Defaults for the evaluator:
-
-DEFAULT_OPERATORS = {
- ast.Add: safe_add,
- ast.Sub: op.sub,
- ast.Mult: safe_mult,
- ast.Div: op.truediv,
- ast.FloorDiv: op.floordiv,
- ast.RShift: safe_rshift,
- ast.LShift: safe_lshift,
- ast.Pow: safe_power,
- ast.Mod: op.mod,
- ast.Eq: op.eq,
- ast.NotEq: op.ne,
- ast.Gt: op.gt,
- ast.Lt: op.lt,
- ast.GtE: op.ge,
- ast.LtE: op.le,
- 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,
- ast.IsNot: lambda x, y: x is not y,
-}
-
-DEFAULT_FUNCTIONS = {
- "rand": random,
- "randint": random_int,
- "int": int,
- "float": float,
- "str": str,
-}
-
-DEFAULT_NAMES = {"True": True, "False": False, "None": None}
-
-ATTR_INDEX_FALLBACK = True
-
-
-########################################
-# And the actual evaluator:
-
-
-class SimpleEval(object): # pylint: disable=too-few-public-methods
- """A very simple expression parser.
- >>> s = SimpleEval()
- >>> s.eval("20 + 30 - ( 10 * 5)")
- 0
- """
-
- expr = ""
-
- def __init__(self, operators=None, functions=None, names=None):
- """
- Create the evaluator instance. Set up valid operators (+,-, etc)
- functions (add, random, get_val, whatever) and names."""
-
- if operators is None:
- operators = DEFAULT_OPERATORS.copy()
- if functions is None:
- functions = DEFAULT_FUNCTIONS.copy()
- if names is None:
- names = DEFAULT_NAMES.copy()
-
- self.operators = operators
- self.functions = functions
- self.names = names
-
- self.nodes = {
- ast.Expr: self._eval_expr,
- ast.Assign: self._eval_assign,
- ast.AugAssign: self._eval_aug_assign,
- ast.Import: self._eval_import,
- ast.Name: self._eval_name,
- ast.UnaryOp: self._eval_unaryop,
- ast.BinOp: self._eval_binop,
- ast.BoolOp: self._eval_boolop,
- ast.Compare: self._eval_compare,
- ast.IfExp: self._eval_ifexp,
- ast.Call: self._eval_call,
- ast.keyword: self._eval_keyword,
- ast.Subscript: self._eval_subscript,
- ast.Attribute: self._eval_attribute,
- ast.Index: self._eval_index,
- ast.Slice: self._eval_slice,
- ast.JoinedStr: self._eval_joinedstr,
- ast.FormattedValue: self._eval_formattedvalue,
- ast.Constant: self._eval_constant,
- }
-
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- # py3.12 deprecated ast.Num, ast.Str, ast.NameConstant
- # https://docs.python.org/3.12/whatsnew/3.12.html#deprecated
- if Num := getattr(ast, "Num", None):
- self.nodes[Num] = self._eval_num
-
- if Str := getattr(ast, "Str", None):
- self.nodes[Str] = self._eval_str
-
- if NameConstant := getattr(ast, "NameConstant", None):
- self.nodes[NameConstant] = self._eval_constant
-
- # Defaults:
-
- self.ATTR_INDEX_FALLBACK = ATTR_INDEX_FALLBACK
-
- # Check for forbidden functions:
-
- for f in self.functions.values():
- if f in DISALLOW_FUNCTIONS:
- raise FeatureNotAvailable("This function {} is a really bad
idea.".format(f))
-
- def __del__(self):
- self.nodes = None
-
- @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 expression, 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
-
- return self._eval(previously_parsed or self.parse(expr))
-
- def _eval(self, node):
- """The internal evaluator used on each node in the parsed tree."""
-
- try:
- handler = self.nodes[type(node)]
- except KeyError:
- raise FeatureNotAvailable(
- "Sorry, {0} is not available in this "
"evaluator".format(type(node).__name__)
- )
-
- return handler(node)
-
- def _eval_expr(self, node):
- return self._eval(node.value)
-
- def _eval_assign(self, node):
- warnings.warn(
- "Assignment ({}) attempted, but this is
ignored".format(self.expr), AssignmentAttempted
- )
- return self._eval(node.value)
-
- def _eval_aug_assign(self, node):
- warnings.warn(
- "Assignment ({}) attempted, but this is
ignored".format(self.expr), AssignmentAttempted
- )
- return self._eval(node.value)
-
- @staticmethod
- def _eval_import(node):
- raise FeatureNotAvailable("Sorry, 'import' is not allowed.")
-
- @staticmethod
- def _eval_num(node):
- return node.n
-
- @staticmethod
- def _eval_str(node):
- if len(node.s) > MAX_STRING_LENGTH:
- raise IterableTooLong(
- "String Literal in statement is too long! ({0}, when {1} is
max)".format(
- len(node.s), MAX_STRING_LENGTH
- )
- )
- return node.s
-
- @staticmethod
- def _eval_constant(node):
- if hasattr(node.value, "__len__") and len(node.value) >
MAX_STRING_LENGTH:
- raise IterableTooLong(
- "Literal in statement is too long! ({0}, when {1} is
max)".format(
- len(node.value), MAX_STRING_LENGTH
- )
- )
- return node.value
-
- def _eval_unaryop(self, node):
- 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):
- 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):
- for value in node.values:
- to_return = self._eval(value)
- if not to_return:
- break
- elif isinstance(node.op, ast.Or):
- for value in node.values:
- to_return = self._eval(value)
- if to_return:
- break
- return to_return
-
- def _eval_compare(self, node):
- right = self._eval(node.left)
- to_return = True
- for operation, comp in zip(node.ops, node.comparators):
- if not to_return:
- break
- left = right
- right = self._eval(comp)
- to_return = self.operators[type(operation)](left, right)
- return to_return
-
- def _eval_ifexp(self, node):
- return self._eval(node.body) if self._eval(node.test) else
self._eval(node.orelse)
-
- def _eval_call(self, node):
- if isinstance(node.func, ast.Attribute):
- func = self._eval(node.func)
- else:
- try:
- func = self.functions[node.func.id]
- except KeyError:
- raise FunctionNotDefined(node.func.id, self.expr)
- except AttributeError:
- raise FeatureNotAvailable("Lambda Functions not implemented")
-
- if func in DISALLOW_FUNCTIONS:
- raise FeatureNotAvailable("This function is forbidden")
-
- return func(
- *(self._eval(a) for a in node.args), **dict(self._eval(k) for k in
node.keywords)
- )
-
- def _eval_keyword(self, node):
- return node.arg, self._eval(node.value)
-
- def _eval_name(self, node):
- try:
- # This happens at least for slicing
- # This is a safe thing to do because it is impossible
- # that there is a true expression assigning to none
- # (the compiler rejects it, so you can't even
- # pass that to ast.parse)
- return self.names[node.id]
-
- except (TypeError, KeyError):
- pass
-
- if callable(self.names):
- try:
- return self.names(node)
- except NameNotDefined:
- pass
- elif not hasattr(self.names, "__getitem__"):
- raise InvalidExpression(
- 'Trying to use name (variable) "{0}"'
- ' when no "names" defined for'
- " evaluator".format(node.id)
- )
-
- if node.id in self.functions:
- return self.functions[node.id]
-
- raise NameNotDefined(node.id, self.expr)
-
- def _eval_subscript(self, node):
- container = self._eval(node.value)
- key = self._eval(node.slice)
- # 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:
- if node.attr.startswith(prefix):
- raise FeatureNotAvailable(
- "Sorry, access to __attributes "
- " or func_ attributes is not available. "
- "({0})".format(node.attr)
- )
- if node.attr in DISALLOW_METHODS:
- raise FeatureNotAvailable(
- "Sorry, this method is not available. "
"({0})".format(node.attr)
- )
- # eval node
- node_evaluated = self._eval(node.value)
-
- # Maybe the base object is an actual object, not just a dict
- try:
- return getattr(node_evaluated, node.attr)
- except (AttributeError, TypeError):
- pass
-
- # TODO: is this a good idea? Try and look for [x] if .x doesn't work?
- if self.ATTR_INDEX_FALLBACK:
- try:
- return node_evaluated[node.attr]
- except (KeyError, TypeError):
- pass
-
- # If it is neither, raise an exception
- raise AttributeDoesNotExist(node.attr, self.expr)
-
- def _eval_index(self, node):
- return self._eval(node.value)
-
- def _eval_slice(self, node):
- lower = upper = step = None
- if node.lower is not None:
- lower = self._eval(node.lower)
- if node.upper is not None:
- upper = self._eval(node.upper)
- if node.step is not None:
- step = self._eval(node.step)
- return slice(lower, upper, step)
-
- def _eval_joinedstr(self, node):
- length = 0
- evaluated_values = []
- for n in node.values:
- val = str(self._eval(n))
- if len(val) + length > MAX_STRING_LENGTH:
- raise IterableTooLong("Sorry, I will not evaluate something
this long.")
- evaluated_values.append(val)
- return "".join(evaluated_values)
-
- def _eval_formattedvalue(self, node):
- if node.format_spec:
- fmt = "{:" + self._eval(node.format_spec) + "}"
- return fmt.format(self._eval(node.value))
- return self._eval(node.value)
-
-
-class EvalWithCompoundTypes(SimpleEval):
- """
- SimpleEval with additional Compound Types, and their respective
- 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)
-
- self.functions.update(list=list, tuple=tuple, dict=dict, set=set)
-
- self.nodes.update(
- {
- ast.Dict: self._eval_dict,
- ast.Tuple: self._eval_tuple,
- ast.List: self._eval_list,
- ast.Set: self._eval_set,
- ast.ListComp: self._eval_comprehension,
- ast.GeneratorExp: self._eval_comprehension,
- ast.DictComp: self._eval_comprehension,
- }
- )
-
- def eval(self, expr, previously_parsed=None):
- # reset _max_count for each eval run
- self._max_count = 0
- return super(EvalWithCompoundTypes, self).eval(expr, previously_parsed)
-
- def _eval_dict(self, node):
- result = {}
-
- for key, value in zip(node.keys, node.values):
- if 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):
- result = []
-
- for item in node.elts:
- if 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)
-
- def _eval_comprehension(self, node):
- if isinstance(node, ast.DictComp):
- to_return = {}
- else:
- to_return = []
-
- extra_names = {}
-
- previous_name_evaller = self.nodes[ast.Name]
-
- def eval_names_extra(node):
- """
- Here we hide our extra scope for within this comprehension
- """
- if node.id in extra_names:
- return extra_names[node.id]
- return previous_name_evaller(node)
-
- self.nodes.update({ast.Name: eval_names_extra})
-
- def recurse_targets(target, value):
- """
- Recursively (enter, (into, (nested, name), unpacking)) = \
- and, (assign, (values, to), each
- """
- if isinstance(target, ast.Name):
- extra_names[target.id] = value
- else:
- for t, v in zip(target.elts, value):
- recurse_targets(t, v)
-
- def do_generator(gi=0):
- g = node.generators[gi]
- for i in self._eval(g.iter):
- self._max_count += 1
-
- if self._max_count > MAX_COMPREHENSION_LENGTH:
- raise IterableTooLong("Comprehension generates too many
elements")
- recurse_targets(g.target, i)
- if all(self._eval(iff) for iff in g.ifs):
- if len(node.generators) > gi + 1:
- do_generator(gi + 1)
- else:
- if isinstance(to_return, dict):
- to_return[self._eval(node.key)] =
self._eval(node.value)
- elif isinstance(to_return, list):
- to_return.append(self._eval(node.elt))
-
- try:
- do_generator()
- finally:
- self.nodes.update({ast.Name: previous_name_evaller})
-
- return to_return
-
-
-def simple_eval(expr, operators=None, functions=None, names=None):
- """Simply evaluate an expression"""
- s = SimpleEval(operators=operators, functions=functions, names=names)
- return s.eval(expr)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/simpleeval-1.0.5/PKG-INFO
new/simpleeval-1.0.7/PKG-INFO
--- old/simpleeval-1.0.5/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/simpleeval-1.0.7/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.3
Name: simpleeval
-Version: 1.0.5
+Version: 1.0.7
Summary: A simple, safe single expression evaluator library.
Project-URL: Source code, https://github.com/danthedeckie/simpleeval
Author-email: Daniel Fairhead <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/simpleeval-1.0.5/pyproject.toml
new/simpleeval-1.0.7/pyproject.toml
--- old/simpleeval-1.0.5/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
+++ new/simpleeval-1.0.7/pyproject.toml 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
[project]
name = "simpleeval"
-version = "1.0.5"
+version = "1.0.7"
requires-python = ">=3.9"
readme = "README.rst"
description = "A simple, safe single expression evaluator library."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/simpleeval-1.0.5/simpleeval.py
new/simpleeval-1.0.7/simpleeval.py
--- old/simpleeval-1.0.5/simpleeval.py 2020-02-02 01:00:00.000000000 +0100
+++ new/simpleeval-1.0.7/simpleeval.py 2020-02-02 01:00:00.000000000 +0100
@@ -59,7 +59,8 @@
- smurfix (Matthias Urlichs) Allow clearing functions / operators / etc
completely
- koenigsley (Mikhail Yeremeyev) documentation typos correction.
- kurtmckee (Kurt McKee) Infrastructure updates
-- edgarrmondragon (Edgar Ramírez-Mondragón) Address Python 3.12+ deprecation
warnings
+- edgarrmondragon (Edgar Ramírez-Mondragón) Address Python 3.12+ deprecation
warnings,
+ performance fixes
- cedk (Cédric Krier) <[email protected]> Allow running tests with Werror
- decorator-factory <[email protected]> More security fixes
- lkruitwagen (Lucas Kruitwagen) Adding support for dict comprehensions
@@ -110,7 +111,7 @@
import types
import warnings
from random import random
-from typing import Type, Dict, Set, Union, Hashable
+from typing import Type, Dict, Set, Union
########################################
# Module wide 'globals'
@@ -132,6 +133,14 @@
"exec",
]
+########################################
+# Tiny helpers:
+
+# Primitive types that are always safe: can't be modules, can't be in
DISALLOW_FUNCTIONS,
+# and don't need recursive container checks. Used as a fast path in
_check_disallowed_items.
+_PRIMITIVE_TYPES = frozenset({int, float, str, bool, type(None), bytes,
complex})
+
+
# Disallow functions:
# This, strictly speaking, is not necessary. These /should/ never be
accessible anyway,
# if DISALLOW_PREFIXES and DISALLOW_METHODS are all right. This is here to
try and help
@@ -617,14 +626,16 @@
Raises FeatureNotAvailable if forbidden content found.
ModuleWrapper instances are allowed (explicit opt-in to module access).
"""
+ # Fast path: primitive scalars are always safe (most common case)
+ if type(item) in _PRIMITIVE_TYPES:
+ return
+
# Allow ModuleWrapper (explicit opt-in to module access)
if isinstance(item, ModuleWrapper):
return
if isinstance(item, types.ModuleType):
raise FeatureNotAvailable("Sorry, modules are not allowed")
- if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS:
- raise FeatureNotAvailable("This function is forbidden")
if isinstance(item, (list, tuple)):
for element in item:
@@ -632,6 +643,8 @@
elif isinstance(item, dict):
for value in item.values():
self._check_disallowed_items(value)
+ elif callable(item) and item in DISALLOW_FUNCTIONS:
+ raise FeatureNotAvailable("This function is forbidden")
@staticmethod
def parse(expr):
@@ -864,7 +877,7 @@
if item is not _ATTR_NOT_FOUND:
if isinstance(item, types.ModuleType):
raise FeatureNotAvailable("Sorry, modules are not allowed in
attribute access")
- if isinstance(item, Hashable) and item in DISALLOW_FUNCTIONS:
+ if callable(item) and item in DISALLOW_FUNCTIONS:
raise FeatureNotAvailable("This function is forbidden")
return item
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/simpleeval-1.0.5/test_simpleeval.py
new/simpleeval-1.0.7/test_simpleeval.py
--- old/simpleeval-1.0.5/test_simpleeval.py 2020-02-02 01:00:00.000000000
+0100
+++ new/simpleeval-1.0.7/test_simpleeval.py 2020-02-02 01:00:00.000000000
+0100
@@ -349,6 +349,15 @@
self.t("foo(mult=2, to_return=4)", 8)
self.t("foo(2, 10)", 20)
+ def test_function_with_list_args(self):
+ # Regression test, makes sure we can pass lists (non-hashable) items as
+ # kwargs to functions.
+
+ def func(*args, **kwargs):
+ return 42
+
+ simple_eval("test(boo=x)", functions={"test": func}, names={"x": [1,
2]})
+
class TestOperators(DRYTest):
"""Test adding in new operators, removing them, make sure it works."""
@@ -661,7 +670,7 @@
"""Functions returning disallowed functions should be blocked"""
def get_evil():
- return exec
+ return exec # pragma: no cover
s = SimpleEval(names={}, functions={"get_evil": get_evil})
@@ -750,9 +759,9 @@
"""Functions returning disallowed methods should be blocked"""
def get_exec_module():
- import os
+ import os # pragma: no cover
- return os
+ return os # pragma: no cover
s = SimpleEval(names={}, functions={"get_os": get_exec_module})
@@ -775,7 +784,7 @@
class Container:
@staticmethod
def get_exec():
- return exec
+ return exec # pragma: no cover
s = SimpleEval(names={"c": Container()})
@@ -849,7 +858,6 @@
def name_handler(node):
if node.id == "evil":
return exec
- raise simpleeval.NameNotDefined(node.id, "")
s = SimpleEval(names=name_handler)
@@ -863,7 +871,6 @@
def name_handler(node):
if node.id == "m":
return os
- raise simpleeval.NameNotDefined(node.id, "")
s = SimpleEval(names=name_handler)
@@ -875,7 +882,7 @@
blocked - they can be executed by the custom function"""
def evil_caller(func):
- return func("print('pwned')")
+ return func("print('pwned')") # pragma: no cover
s = SimpleEval(names={"evil": exec}, functions={"evil_caller":
evil_caller})
@@ -888,19 +895,19 @@
import os
def os_caller(mod):
- return mod.system("id")
+ return mod.system("id") # pragma: no cover
s = SimpleEval(names={"m": os}, functions={"os_caller": os_caller})
with self.assertRaises(FeatureNotAvailable):
- s.eval("os_caller(m)")
+ s.eval("os_caller(m)") # pragma: no cover
def test_forbidden_function_in_list_passed_to_custom_function(self):
"""Forbidden functions in containers passed to custom functions
should be blocked"""
def extract_and_call(items):
- return items[0]("print('pwned')")
+ return items[0]("print('pwned')") # pragma: no cover
s = SimpleEval(names={"funcs": [exec, eval]}, functions={"extract":
extract_and_call})
@@ -913,7 +920,7 @@
import os
def extract_and_use(items):
- return items[0].system("id")
+ return items[0].system("id") # pragma: no cover
s = SimpleEval(names={"mods": [os.path, os]}, functions={"extract":
extract_and_use})
@@ -925,7 +932,7 @@
be blocked"""
def extract_and_call(d):
- return d["bad"]("print('pwned')")
+ return d["bad"]("print('pwned')") # pragma: no cover
s = SimpleEval(
names={"funcs": {"bad": exec, "good": print}},
functions={"extract": extract_and_call}
@@ -939,7 +946,7 @@
import os
def extract_and_use(d):
- return d["m"].system("id")
+ return d["m"].system("id") # pragma: no cover
s = SimpleEval(
names={"mods": {"m": os, "p": os.path}}, functions={"extract":
extract_and_use}