├── .bumpversion.cfg ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.rst ├── dicteval ├── __init__.py ├── builtin_language.py ├── evaluator.py ├── exceptions.py └── language_specification.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── conftest.py └── test_eval.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.0.2 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [bumpversion:file:setup.py] 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pytest_cache/ 2 | *.egg-info/ 3 | dist/ 4 | build/ 5 | .idea 6 | .eggs/ 7 | .venv/ 8 | .env/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | arch: 3 | - amd64 4 | - pp64le 5 | python: 6 | - "3.6" 7 | install: 8 | - pip install pipenv 9 | - pipenv install --dev 10 | script: 11 | - pipenv run python setup.py test 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Pull Requests are always welcome. 4 | 2. Follow PEP-8 coding style (except for the 80 columns line length limitation) 5 | 3. Write automated tests for your code. 6 | 4. Write specific documentation to your contribution at README.md. 7 | 5. Run all tests before opening a Pull Request. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Osvaldo Santana Neto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | 8 | [dev-packages] 9 | bumpversion = "*" 10 | pytest = "*" 11 | pytest-runner = "*" 12 | 13 | [requires] 14 | python_version = "3.7" 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "4192ce941db7788b670499e3153836b94a119ba1b1365d6e639108dafe45cf0e" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": { 20 | "atomicwrites": { 21 | "hashes": [ 22 | "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197", 23 | "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a" 24 | ], 25 | "version": "==1.4.0" 26 | }, 27 | "attrs": { 28 | "hashes": [ 29 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 30 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 31 | ], 32 | "version": "==20.3.0" 33 | }, 34 | "bumpversion": { 35 | "hashes": [ 36 | "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", 37 | "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57" 38 | ], 39 | "index": "pypi", 40 | "version": "==0.5.3" 41 | }, 42 | "importlib-metadata": { 43 | "hashes": [ 44 | "sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef", 45 | "sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9" 46 | ], 47 | "markers": "python_version < '3.8'", 48 | "version": "==4.0.0" 49 | }, 50 | "more-itertools": { 51 | "hashes": [ 52 | "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced", 53 | "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713" 54 | ], 55 | "version": "==8.7.0" 56 | }, 57 | "pluggy": { 58 | "hashes": [ 59 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 60 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 61 | ], 62 | "version": "==0.13.1" 63 | }, 64 | "py": { 65 | "hashes": [ 66 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 67 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 68 | ], 69 | "index": "pypi", 70 | "version": "==1.10.0" 71 | }, 72 | "pytest": { 73 | "hashes": [ 74 | "sha256:212be78a6fa5352c392738a49b18f74ae9aeec1040f47c81cadbfd8d1233c310", 75 | "sha256:6f6c1efc8d0ccc21f8f6c34d8330baca883cf109b66b3df954b0a117e5528fb4" 76 | ], 77 | "index": "pypi", 78 | "version": "==3.9.2" 79 | }, 80 | "pytest-runner": { 81 | "hashes": [ 82 | "sha256:d23f117be39919f00dd91bffeb4f15e031ec797501b717a245e377aee0f577be", 83 | "sha256:d987fec1e31287592ffe1cb823a8c613c533db4c6aaca0ee1191dbc91e2fcc61" 84 | ], 85 | "index": "pypi", 86 | "version": "==4.2" 87 | }, 88 | "six": { 89 | "hashes": [ 90 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 91 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 92 | ], 93 | "version": "==1.15.0" 94 | }, 95 | "typing-extensions": { 96 | "hashes": [ 97 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 98 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 99 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 100 | ], 101 | "markers": "python_version < '3.8'", 102 | "version": "==3.7.4.3" 103 | }, 104 | "zipp": { 105 | "hashes": [ 106 | "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", 107 | "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" 108 | ], 109 | "version": "==3.4.1" 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | dicteval 2 | ======== 3 | 4 | Library to evaluate expressions in dict/json objects. 5 | 6 | 7 | Requirements 8 | ------------ 9 | 10 | * Python 3.6+ 11 | 12 | 13 | Basic Usage 14 | ----------- 15 | 16 | Module ``dicteval`` will evaluate basic types with no modifications but it will 17 | evaluate dicts (or json objects) containing keys started with ``=`` (equal) 18 | symbol as an expression: 19 | 20 | >>> from dicteval import dicteval 21 | >>> dicteval(3) 22 | 3 23 | >>> dicteval([3, 5]) 24 | [3, 5] 25 | >>> dicteval((5, 3)) 26 | [5, 3] 27 | >>> dicteval({"=sum": [3, 5]}) 28 | 8 29 | >>> dicteval({"=": 5}) # = symbol alone is a 'nop' function 30 | 5 31 | 32 | You can provide a dictionary with context to be used during evaluation process. 33 | 34 | >>> dicteval({"=": "!{var}"}, context={"var": 1.0}) 35 | 1.0 36 | 37 | You can also wrap your string content with ``@{}`` to force a Python ``eval()`` 38 | with the context provided: 39 | 40 | >>> dicteval({"=sum": [3, "@{var + 2}"]}, context={"var": 3}) 41 | 8 42 | 43 | .. warning:: 44 | This functionality will be removed (or changed) in future releases for 45 | security reasons. 46 | 47 | 48 | Functions 49 | --------- 50 | 51 | You can use the following builtin functions in your expressions: 52 | 53 | 54 | Function ``=any`` 55 | ''''''''''''''''' 56 | 57 | Returns ``True`` if any element of sequence is true. 58 | 59 | >>> dicteval({"=any": [1, 2, 3]}) 60 | True 61 | >>> dicteval({"=any": [0, 0]}) 62 | False 63 | 64 | 65 | Function ``=eq`` 66 | '''''''''''''''' 67 | 68 | Returns ``True`` if all elements of sequence are equals: 69 | 70 | >>> dicteval({"=eq": [1, 1, 1, 1]}) 71 | True 72 | 73 | 74 | Function ``=if`` 75 | '''''''''''''''' 76 | 77 | Evaluates condition and returns first value if true, otherwise, returns second value. 78 | If no false value is supplied, it is assumed to be ``None``. 79 | 80 | >>> dicteval({"=if": [{"=": "@{var > 5}"}, "yes", "no"]}, context={"var": 6}) 81 | 'yes' 82 | >>> dicteval({"=if": [{"=": "@{var > 5}"}, "yes", "no"]}, context={"var": 4}) 83 | 'no' 84 | >>> dicteval({"=if": [{"=": "@{var > 5}"}, "yes"]}, context={"var": 4}) 85 | 86 | 87 | Function ``=neq`` 88 | ''''''''''''''''' 89 | 90 | Returns ``True`` if elements of sequence are different: 91 | 92 | >>> dicteval({"=neq": [1, 1, 1, 5]}) 93 | True 94 | 95 | 96 | Function ``=`` (or ``nop``) 97 | ''''''''''''''''''''''''''' 98 | 99 | Returns the same values passed as arguments: 100 | 101 | >>> dicteval({"=": [1, 2, 3, 4]}) 102 | [1, 2, 3, 4] 103 | >>> dicteval({"=nop": "spam"}) 104 | 'spam' 105 | 106 | 107 | Function ``=not`` 108 | ''''''''''''''''' 109 | 110 | Returns the boolean inverse of argument: 111 | 112 | >>> dicteval({"=not": False}) 113 | True 114 | >>> dicteval({"=not": True}) 115 | False 116 | >>> dicteval({"=not": None}) 117 | True 118 | >>> dicteval({"=not": "XYZ"}) 119 | False 120 | 121 | 122 | Function ``=sum`` 123 | ''''''''''''''''' 124 | 125 | Returns a number with the sum of arguments: 126 | 127 | >>> dicteval({"=sum": [3, 5]}) 128 | 8 129 | 130 | 131 | Function ``=mul`` 132 | ''''''''''''''''' 133 | 134 | Returns a number with the product of arguments: 135 | 136 | >>> dicteval({"=mul": [3, 5]}) 137 | 15 138 | 139 | 140 | Function ``=all`` 141 | ''''''''''''''''' 142 | 143 | Return True if all elements of the iterable are true (or if the iterable is empty) 144 | 145 | >>> dicteval({"=all": (True, False)}) 146 | False 147 | >>> dicteval({"=all": (True, True)}) 148 | True 149 | 150 | 151 | Function ``=divmod`` 152 | '''''''''''''''''''' 153 | 154 | Returns a tuple containing the quotient and remainder after division: 155 | 156 | >>> dicteval({"=divmod": [8,3]}) 157 | (2, 2) 158 | >>> dicteval({"=divmod": [7.5,2.5]}) 159 | (3.0, 0.0) 160 | 161 | 162 | Function ``=zip`` 163 | ''''''''''''''''' 164 | 165 | Return list of aggregate tuples constructed from elements of multiple iterables. 166 | 167 | >>> dicteval({"=zip": [[1, 2, 3], [4, 5], [6, 7, 8, 9]]}) 168 | [(1, 4, 6), (2, 5, 7)] 169 | 170 | 171 | To Do 172 | ----- 173 | 174 | - Add more functions to the builtin language 175 | 176 | 177 | Contribute 178 | ---------- 179 | 180 | To contribute to `dicteval`: 181 | 182 | 1. Clone this repository and `cd` into it 183 | 2. Install dev dependencies with [pipenv](https://github.com/pypa/pipenv) 184 | ```bash 185 | pipenv install --dev 186 | ``` 187 | 3. Create a branch, like `git checkout -b [feature_name]` 188 | 4. Git commit changes 189 | 5. Pull request 190 | 191 | 192 | License 193 | ------- 194 | 195 | This software is licensed under MIT license. 196 | -------------------------------------------------------------------------------- /dicteval/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | from .exceptions import FunctionNotFound 4 | from .builtin_language import BuiltinLanguage 5 | from .evaluator import Evaluator 6 | 7 | dicteval = Evaluator(BuiltinLanguage) 8 | 9 | def jsoneval(string): 10 | return dicteval(json.loads(string)) 11 | -------------------------------------------------------------------------------- /dicteval/builtin_language.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import functools 3 | from .language_specification import LanguageSpecification 4 | 5 | class BuiltinLanguage(LanguageSpecification): 6 | def function_any(self, value, evaluator, context): 7 | return any(evaluator(v, context) for v in value) 8 | 9 | def function_all(self, value, evaluator, context): 10 | return all(evaluator(v, context) for v in value) 11 | 12 | def function_eq(self, value, evaluator, context): 13 | value = [evaluator(v, context) for v in value] 14 | return not value or value.count(value[0]) == len(value) 15 | 16 | def function_if(self, value, evaluator, context): 17 | values = [evaluator(v, context) for v in value] 18 | condition, t = values[0:2] 19 | f = values[2] if len(values) > 2 else None 20 | return t if condition else f 21 | 22 | def function_neq(self, value, evaluator, context): 23 | return not self.function_eq(value, evaluator, context) 24 | 25 | def function_nop(self, value, evaluator, context): 26 | return evaluator(value, context) 27 | 28 | def function_not(self, value, evaluator, context): 29 | return not evaluator(value, context) 30 | 31 | def function_sum(self, value, evaluator, context): 32 | return sum(evaluator(v, context) for v in value) 33 | 34 | def function_mul(self, value, evaluator, context): 35 | return functools.reduce(operator.mul, (evaluator(v, context) for v in value)) 36 | 37 | def function_divmod(self, value, evaluator, context): 38 | return divmod(*evaluator(value, context)) 39 | 40 | def function_map(self, func, value, evaluator, context): 41 | return [func(e) for e in [evaluator(v, context) for v in value]] 42 | 43 | def function_filter(self, func, value, evaluator, context): 44 | return list(filter(func, (evaluator(v, context) for v in value))) 45 | 46 | def function_zip(self, value, evaluator, context): 47 | lists = [evaluator(v, context) for v in value] 48 | return list(zip(*lists)) 49 | -------------------------------------------------------------------------------- /dicteval/evaluator.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Evaluator: 4 | def __init__(self, language_spec): 5 | self.language = language_spec() 6 | 7 | def __call__(self, expr, context=None): 8 | if context is None: 9 | context = {} 10 | 11 | if isinstance(expr, dict): 12 | expression_keys = [k for k in expr if k.startswith("=")] 13 | if len(expression_keys) != 1: 14 | raise ValueError("Invalid expression") 15 | 16 | key = expression_keys[0] 17 | value = expr[key] 18 | 19 | if isinstance(value, dict): 20 | value = self(value, context) 21 | 22 | func = self.language[key[1:]] 23 | 24 | if func.__name__ in ['function_map', 'function_filter']: 25 | coll_func = re.search(r'(map|filter)\((.*)\)', key).groups()[1] 26 | return func(eval(coll_func), value, self, context) 27 | 28 | return func(value, self, context) 29 | 30 | if isinstance(expr, (list, tuple)): 31 | return [self(v, context) for v in expr] 32 | 33 | # TODO: implement a safe eval here 34 | if isinstance(expr, str): 35 | if expr.startswith("@{") and expr.endswith("}"): 36 | return eval(expr[2:-1], {}, context) 37 | if expr.startswith("!{") and expr.endswith("}"): 38 | return context[expr[2:-1]] 39 | 40 | return expr 41 | -------------------------------------------------------------------------------- /dicteval/exceptions.py: -------------------------------------------------------------------------------- 1 | class EvaluationException(Exception): 2 | pass 3 | 4 | 5 | class FunctionNotFound(EvaluationException): 6 | pass 7 | -------------------------------------------------------------------------------- /dicteval/language_specification.py: -------------------------------------------------------------------------------- 1 | from .exceptions import FunctionNotFound 2 | 3 | 4 | class LanguageSpecification: 5 | def __getitem__(self, item): 6 | if not item: 7 | item = "nop" 8 | if item.startswith('map'): 9 | item = "map" 10 | if item.startswith('filter'): 11 | item = "filter" 12 | try: 13 | return getattr(self, f"function_{item}") 14 | except AttributeError: 15 | raise FunctionNotFound(f"Function {item} not found.") 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | 4 | [tool:pytest] 5 | addopts = --verbose --doctest-glob='*.rst' 6 | python_files = tests/*.py 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import io 4 | import os 5 | 6 | from setuptools import find_packages, setup 7 | 8 | NAME = 'dicteval' 9 | DESCRIPTION = 'Evaluate expressions in dict/json objects' 10 | URL = 'https://github.com/osantana/dicteval' 11 | EMAIL = 'dicteval@osantana.me' 12 | AUTHOR = 'Osvaldo Santana Neto' 13 | REQUIRES_PYTHON = '>=3.7.0' 14 | VERSION = "0.0.6" 15 | 16 | here = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: 19 | long_description = '\n' + f.read() 20 | 21 | setup( 22 | name=NAME, 23 | version=VERSION, 24 | description=DESCRIPTION, 25 | long_description=long_description, 26 | author=AUTHOR, 27 | author_email=EMAIL, 28 | python_requires=REQUIRES_PYTHON, 29 | url=URL, 30 | packages=find_packages(exclude=('tests',)), 31 | install_requires=[], 32 | include_package_data=True, 33 | license='MIT', 34 | classifiers=[ 35 | 'License :: OSI Approved :: MIT License', 36 | 'Programming Language :: Python', 37 | 'Programming Language :: Python :: 3', 38 | 'Programming Language :: Python :: 3.7', 39 | 'Programming Language :: Python :: Implementation :: CPython', 40 | 'Programming Language :: Python :: Implementation :: PyPy' 41 | ], 42 | setup_requires=['pytest-runner'], 43 | tests_require=['pytest'], 44 | ) 45 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osantana/dicteval/cf6cba6f0ad6ff82a25215ca57a1d4d1fa24db62/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def context(): 6 | class PlainObject: 7 | attr = "object.attr" 8 | 9 | return {"varint": 1, "varstr": "str", "varobj": PlainObject()} 10 | -------------------------------------------------------------------------------- /tests/test_eval.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from dicteval import BuiltinLanguage, dicteval, jsoneval 4 | from dicteval.exceptions import FunctionNotFound 5 | 6 | 7 | @pytest.mark.parametrize("expression,result", [ 8 | (3, 3), 9 | ([3], [3]), 10 | ([3, 5], [3, 5]), 11 | ((3, 5), [3, 5]), 12 | ("x", "x"), 13 | ({"=": 3}, 3), 14 | ({"=": [3, 5]}, [3, 5]), 15 | ({"=": 3, "ignore": 5}, 3), 16 | ({"=sum": [3, 5]}, 8), 17 | ({"=sum": (3, 5)}, 8), 18 | ({"=sum": {"=": [3, 5]}}, 8), 19 | ({"=mul": (5, 3, 2, -1)}, -30), 20 | ({"=map(lambda x: x**2)": [5, 6, -7]},[25,36,49]), 21 | ({"=filter(lambda x: x>20)": [10, 18, 22,3,-10,50,800]},[22,50,800]), 22 | ({"=all": (True, True, True)}, True), 23 | ({"=all": {"=": (True, True, True)}}, True), 24 | ({"=all": (True, False, True)}, False), 25 | ({"=all": (False, False, False)}, False), 26 | ({"=divmod": (8, 3)}, (2, 2)), 27 | ({"=divmod": [8, 3]}, (2, 2)), 28 | ({"=divmod": {"=": [8, 3]}}, (2, 2)), 29 | ({"=divmod": (7.5, 2.5)}, (3.0, 0.0)), 30 | ({"=zip": ([1, 2, 3], [4, 5], {"=": [6, 7, 8, 9]})}, [(1, 4, 6), (2, 5, 7)]), 31 | ]) 32 | def test_basic_eval(expression, result): 33 | assert dicteval(expression) == result 34 | 35 | 36 | def test_context_eval(context): 37 | assert dicteval('''@{varobj.attr + " = 'object.attr'"}''', context) == "object.attr = 'object.attr'" 38 | 39 | 40 | def test_invalid_expression_object_with_no_result_key(): 41 | with pytest.raises(ValueError): 42 | dicteval({"no_result_error": 0}) 43 | 44 | 45 | def test_json_loads(): 46 | assert jsoneval('{"=": [true, false, null]}') == [True, False, None] 47 | 48 | 49 | @pytest.mark.parametrize("fn,args,result", [ 50 | ("any", (1, 2, 3), True), 51 | ("any", (0, 0), False), 52 | ("eq", (1, 1, 1, 1, 1), True), 53 | ("eq", (1, 1, 5, 1, 1), False), 54 | ("neq", (1, 1, 1, 1, 1), False), 55 | ("neq", (1, 1, 5, 1, 1), True), 56 | ("nop", 4, 4), 57 | ("not", True, False), 58 | ("not", False, True), 59 | ("sum", (1, 2), 3), 60 | ("mul", (2, 4), 8), 61 | ("all", tuple(), True), 62 | ("all", (True, True), True), 63 | ("all", (True, False), False), 64 | ("divmod", (8, 3), (2, 2)), 65 | ("divmod", (7.5, 2.5), (3.0, 0.0)), 66 | ]) 67 | def test_buitin_language(fn, args, result, context): 68 | language = BuiltinLanguage() 69 | assert language[fn](args, dicteval, context) == result 70 | 71 | 72 | def test_function_not_found_error(): 73 | language = BuiltinLanguage() 74 | with pytest.raises(FunctionNotFound): 75 | return language["not_found"] 76 | --------------------------------------------------------------------------------