Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failed to recompute the values of the contract condition #229

Closed
leshchenko1979 opened this issue Sep 13, 2021 · 9 comments · Fixed by #232
Closed

Failed to recompute the values of the contract condition #229

leshchenko1979 opened this issue Sep 13, 2021 · 9 comments · Fixed by #232

Comments

@leshchenko1979
Copy link

I have the following code:

@ensure(
    lambda result: len(result) > 30_000, "Too few lines"
)
@ensure(
    lambda result: all(
        result[col].notna().sum() / len(result) > 0.3 for col in FILTER_FEATURES
    ),
    "Filter features are not filled in properly",
)
def load_listings(regions=None) -> pd.DataFrame:
...

If the first contract fails, I'm getting a RuntimeError instead of a ViolationError:

Traceback (most recent call last):
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
    response = self.full_dispatch_request()
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
    rv = self.dispatch_request()
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
  File "/workspace/main.py", line 43, in main
    listings = load_listings()
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/icontract/_checkers.py", line 646, in wrapper
    violation_error = _assert_postconditions(
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/icontract/_checkers.py", line 396, in _assert_postconditions
    exception = _create_violation_error(contract=contract, resolved_kwargs=resolved_kwargs)
  File "/layers/google.python.pip/pip/lib/python3.9/site-packages/icontract/_checkers.py", line 192, in _create_violation_error
    raise RuntimeError(''.join(parts)) from err
RuntimeError: Failed to recompute the values of the contract condition:
File /workspace/main.py, line 65 in <module>:
Too few lines: lambda result: len(result) > 30_000

I also made the second one fail with pytest, and the test output was:

./test_minos.py::test_loading Failed: [undefined]RuntimeError: Failed to recompute the values of the contract condition:
File c:\Users\leshc\flipio\minos\main.py, line 68 in <module>:
Filter features are not filled in properly: lambda result: all(
        result[col].notna().sum() / len(result) > 0.3 for col in FILTER_FEATURES
    )
contract = <icontract._types.Contract object at 0x000001C5B46DACA0>
resolved_kwargs = {'_ARGS': (), '_KWARGS': {}, 'regions': None, 'result':        repair  house_material  windows_type  room_type
0      ...         NaN           NaN        NaN
39999     NaN             NaN           NaN        NaN

[40000 rows x 4 columns]}

    def _create_violation_error(contract: Contract, resolved_kwargs: Mapping[str, Any]) -> BaseException:
        """Create the violation error based on the violated contract."""
        exception = None  # type: Optional[BaseException]
    
        if contract.error is None:
            try:
>               msg = icontract._represent.generate_message(contract=contract, resolved_kwargs=resolved_kwargs)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_checkers.py:181: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

contract = <icontract._types.Contract object at 0x000001C5B46DACA0>
resolved_kwargs = {'_ARGS': (), '_KWARGS': {}, 'regions': None, 'result':        repair  house_material  windows_type  room_type
0      ...         NaN           NaN        NaN
39999     NaN             NaN           NaN        NaN

[40000 rows x 4 columns]}

    def generate_message(contract: Contract, resolved_kwargs: Mapping[str, Any]) -> str:
        """Generate the message upon contract violation."""
        parts = []  # type: List[str]
    
        if contract.location is not None:
            parts.append("{}:\n".format(contract.location))
    
        if contract.description is not None:
            parts.append("{}: ".format(contract.description))
    
        lambda_inspection = None  # type: Optional[ConditionLambdaInspection]
        if not is_lambda(a_function=contract.condition):
            condition_text = contract.condition.__name__
        else:
            # We need to extract the source code corresponding to the decorator since inspect.getsource() is broken with
            # lambdas.
            lambda_inspection = inspect_lambda_condition(condition=contract.condition)
            assert lambda_inspection is not None, \
                "Unexpected no lambda inspection for condition: {}".format(contract.condition)
            condition_text = lambda_inspection.text
    
        parts.append(condition_text)
    
>       repr_vals = repr_values(
            condition=contract.condition,
            lambda_inspection=lambda_inspection,
            resolved_kwargs=resolved_kwargs,
            a_repr=contract._a_repr)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_represent.py:542: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

condition = <function <lambda> at 0x000001C5B46E48B0>
lambda_inspection = <icontract._represent.ConditionLambdaInspection object at 0x000001C5B484EF40>
resolved_kwargs = {'_ARGS': (), '_KWARGS': {}, 'regions': None, 'result':        repair  house_material  windows_type  room_type
0      ...         NaN           NaN        NaN
39999     NaN             NaN           NaN        NaN

[40000 rows x 4 columns]}
a_repr = <reprlib.Repr object at 0x000001C5A34D9BB0>

    def repr_values(condition: Callable[..., bool], lambda_inspection: Optional[ConditionLambdaInspection],
                    resolved_kwargs: Mapping[str, Any], a_repr: reprlib.Repr) -> List[str]:
        """
        Represent function arguments and frame values in the error message on contract breach.
    
        :param condition: condition function of the contract
        :param lambda_inspection:
            inspected lambda AST node corresponding to the condition function (None if the condition was not given as a
            lambda function)
        :param resolved_kwargs: arguments put in the function call
        :param a_repr: representation instance that defines how the values are represented.
        :return: list of value representations
        """
        # Hide _ARGS and _KWARGS if they are not part of the condition for better readability
        if '_ARGS' in resolved_kwargs or '_KWARGS' in resolved_kwargs:
            parameters = inspect.signature(condition).parameters
            malleable_kwargs = cast(
                MutableMapping[str, Any],
                resolved_kwargs.copy()  # type: ignore
            )
    
            if '_ARGS' not in parameters:
                malleable_kwargs.pop('_ARGS', None)
    
            if '_KWARGS' not in parameters:
                malleable_kwargs.pop('_KWARGS', None)
    
            selected_kwargs = cast(Mapping[str, Any], malleable_kwargs)
        else:
            selected_kwargs = resolved_kwargs
    
        # Don't use ``resolved_kwargs`` from this point on.
        # ``selected_kwargs`` is meant to be used instead for better readability of error messages.
    
        if is_lambda(a_function=condition):
            assert lambda_inspection is not None, "Expected a lambda inspection when given a condition as a lambda function"
        else:
            assert lambda_inspection is None, "Expected no lambda inspection in a condition given as a non-lambda function"
    
        reprs = None  # type: Optional[MutableMapping[str, Any]]
    
        if lambda_inspection is not None:
            variable_lookup = collect_variable_lookup(condition=condition, resolved_kwargs=selected_kwargs)
    
            recompute_visitor = icontract._recompute.Visitor(variable_lookup=variable_lookup)
    
>           recompute_visitor.visit(node=lambda_inspection.node.body)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_represent.py:463: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Call object at 0x000001C5B486ABB0>

    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
>       return visitor(node)

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.2032.0_x64__qbz5n2kfra8p0\lib\ast.py:407: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Call object at 0x000001C5B486ABB0>

    def visit_Call(self, node: ast.Call) -> Any:
        """Visit the function and the arguments and finally make the function call with them."""
        func = self.visit(node=node.func)
    
        # Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
        if func == PLACEHOLDER:
            return PLACEHOLDER
    
        if not callable(func):
            raise ValueError("Unexpected call to a non-calllable during the re-computation: {}".format(func))
    
        if inspect.iscoroutinefunction(func):
            raise ValueError(
                ("Unexpected coroutine function {} as a condition of a contract. "
                 "You must specify your own error if the condition of your contract is a coroutine function."
                 ).format(func))
    
        # Short-circuit tracing the all quantifier over a generator expression
        # yapf: disable
        if (
                func == builtins.all  # pylint: disable=comparison-with-callable
                and len(node.args) == 1
                and isinstance(node.args[0], ast.GeneratorExp)
        ):
            # yapf: enable
>           result = self._trace_all_with_generator(func=func, node=node)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:567: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
func = <built-in function all>, node = <ast.Call object at 0x000001C5B486ABB0>

    def _trace_all_with_generator(self, func: Callable[..., Any], node: ast.Call) -> Any:
        """Re-write the all call with for loops to trace the first offending item, if any."""
        assert func == builtins.all  # pylint: disable=comparison-with-callable
        assert len(node.args) == 1
        assert isinstance(node.args[0], ast.GeneratorExp)
    
        # Try the happy path first
    
        # Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
>       recomputed_arg = self.visit(node=node.args[0])

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:733: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.GeneratorExp object at 0x000001C5B486AAC0>

    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
>       return visitor(node)

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.2032.0_x64__qbz5n2kfra8p0\lib\ast.py:407: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.GeneratorExp object at 0x000001C5B486AAC0>

    def visit_GeneratorExp(self, node: ast.GeneratorExp) -> Any:
        """Compile the generator expression as a function and call it."""
        # NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION:
        # Re-computing the comprehensions would be too slow. Therefore we re-compile the comprehension and call
        # the compiled code directly.
        #
        # However, we still want to report the values of the variables unrelated to the comprehension back
        # to the user. Therefore we introduce PLACEHOLDER's and propagate them through re-computation in all
        # the visit methods.
    
        # The following visits propagate the visitation to descendant nodes.
        # However, as we re-compute the comprehension *via* re-compilation & execution,
        # the results of the visits are all PLACEHOLDER's.
    
        # NOTE ABOUT NAME #x1F812 VALUE STACKING:
        # We need to make a copy of name #x1F812 value mapping since we need to add targets as placeholders
        # while we visit the comprehension. For example, as we compute comprehensions through re-compilation
        # and not through manual re-computation, we can not re-compute nested comprehensions.
        #
        # However, when our visit of comprehension is finished, the targets are not valid any more,
        # so we need to remove them from the mapping.
        #
        # Finally, we compute the comprehension with the original name #x1F812 value mapping by using
        # re-compilation. This final step is skipped if any of the names involved in the comprehension are
        # PLACEHOLDER's.
    
        old_name_to_value = copy.copy(self._name_to_value)
        for target_name in _collect_stored_names([generator.target for generator in node.generators]):
            self._name_to_value[target_name] = PLACEHOLDER
    
>       self.visit(node.elt)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:846: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Compare object at 0x000001C5B486AB20>

    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
>       return visitor(node)

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.2032.0_x64__qbz5n2kfra8p0\lib\ast.py:407: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Compare object at 0x000001C5B486AB20>

    def visit_Compare(self, node: ast.Compare) -> Any:
        """Recursively visit the comparators and apply the operations on them."""
>       left = self.visit(node=node.left)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:499: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.BinOp object at 0x000001C5B486AA00>

    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
>       return visitor(node)

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.2032.0_x64__qbz5n2kfra8p0\lib\ast.py:407: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.BinOp object at 0x000001C5B486AA00>

    def visit_BinOp(self, node: ast.BinOp) -> Any:
        """Recursively visit the left and right operand, respectively, and apply the operation on the results."""
        left = self.visit(node=node.left)
>       right = self.visit(node=node.right)

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:441: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Call object at 0x000001C5B486A8B0>

    def visit(self, node):
        """Visit a node."""
        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, self.generic_visit)
>       return visitor(node)

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.9_3.9.2032.0_x64__qbz5n2kfra8p0\lib\ast.py:407: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <icontract._recompute.Visitor object at 0x000001C5B48575E0>
node = <ast.Call object at 0x000001C5B486A8B0>

    def visit_Call(self, node: ast.Call) -> Any:
        """Visit the function and the arguments and finally make the function call with them."""
        func = self.visit(node=node.func)
    
        # Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
        if func == PLACEHOLDER:
            return PLACEHOLDER
    
        if not callable(func):
            raise ValueError("Unexpected call to a non-calllable during the re-computation: {}".format(func))
    
        if inspect.iscoroutinefunction(func):
            raise ValueError(
                ("Unexpected coroutine function {} as a condition of a contract. "
                 "You must specify your own error if the condition of your contract is a coroutine function."
                 ).format(func))
    
        # Short-circuit tracing the all quantifier over a generator expression
        # yapf: disable
        if (
                func == builtins.all  # pylint: disable=comparison-with-callable
                and len(node.args) == 1
                and isinstance(node.args[0], ast.GeneratorExp)
        ):
            # yapf: enable
            result = self._trace_all_with_generator(func=func, node=node)
    
            if result is PLACEHOLDER:
                return PLACEHOLDER
        else:
            args = []  # type: List[Any]
            for arg_node in node.args:
                if isinstance(arg_node, ast.Starred):
                    args.extend(self.visit(node=arg_node))
                else:
                    args.append(self.visit(node=arg_node))
    
            kwargs = dict()  # type: Dict[Union[str, Placeholder], Any]
            for keyword in node.keywords:
                if keyword.arg is None:
                    kw = self.visit(node=keyword.value)
                    for key, val in kw.items():
                        kwargs[key] = val
    
                else:
                    kwargs[keyword.arg] = self.visit(node=keyword.value)
    
            # Please see "NOTE ABOUT PLACEHOLDERS AND RE-COMPUTATION"
>           if PLACEHOLDER in args or PLACEHOLDER in kwargs or PLACEHOLDER in kwargs.values():

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_recompute.py:590: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self =        repair  house_material  windows_type  room_type
0       False           False         False      False
1       ...        False         False      False
39999   False           False         False      False

[40000 rows x 4 columns]

    @final
    def __nonzero__(self):
>       raise ValueError(
            f"The truth value of a {type(self).__name__} is ambiguous. "
            "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
        )
E       ValueError: The truth value of a DataFrame is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\pandas\core\generic.py:1537: ValueError

The above exception was the direct cause of the following exception:

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x000001C5B46EC0D0>

    def test_loading(monkeypatch):
        def fake_load_psql(*args, **kwargs):
            return pd.DataFrame([{} for _ in range(40_000)], columns=FILTER_FEATURES)
    
        monkeypatch.setattr(main, "load_psql", fake_load_psql)
>       load_listings()

test_minos.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_checkers.py:646: in wrapper
    violation_error = _assert_postconditions(
..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_checkers.py:396: in _assert_postconditions
    exception = _create_violation_error(contract=contract, resolved_kwargs=resolved_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

contract = <icontract._types.Contract object at 0x000001C5B46DACA0>
resolved_kwargs = {'_ARGS': (), '_KWARGS': {}, 'regions': None, 'result':        repair  house_material  windows_type  room_type
0      ...         NaN           NaN        NaN
39999     NaN             NaN           NaN        NaN

[40000 rows x 4 columns]}

    def _create_violation_error(contract: Contract, resolved_kwargs: Mapping[str, Any]) -> BaseException:
        """Create the violation error based on the violated contract."""
        exception = None  # type: Optional[BaseException]
    
        if contract.error is None:
            try:
                msg = icontract._represent.generate_message(contract=contract, resolved_kwargs=resolved_kwargs)
            except Exception as err:
                parts = ["Failed to recompute the values of the contract condition:\n"]
                if contract.location is not None:
                    parts.append("{}:\n".format(contract.location))
    
                if contract.description is not None:
                    parts.append("{}: ".format(contract.description))
    
                parts.append(icontract._represent.represent_condition(condition=contract.condition))
    
>               raise RuntimeError(''.join(parts)) from err
E               RuntimeError: Failed to recompute the values of the contract condition:
E               File c:\Users\leshc\flipio\minos\main.py, line 68 in <module>:
E               Filter features are not filled in properly: lambda result: all(
E                       result[col].notna().sum() / len(result) > 0.3 for col in FILTER_FEATURES
E                   )

..\..\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\icontract\_checkers.py:192: RuntimeError
@mristin
Copy link
Collaborator

mristin commented Sep 15, 2021

Hi @leshchenko1979 ,
Thank you a lot for submitting the bug! I think the notation 30_000 might be the cause. This week I am too busy, but I can have a look next week. Would that be OK with you?

@leshchenko1979
Copy link
Author

Absolutely no worries, thanks. I'll also try the 30000 notation and let you know.

@dycw
Copy link

dycw commented Sep 15, 2021

Just been looking at this. The error seems to be quite widespread in my (attempted) use-case. Here's a minimal, reproducible example:

from icontract import require
from numpy import arange


@require(lambda x: len(x) >= 10)
def identity(x):
    return x


identity(arange(5))

with

❯ python foo.py
Traceback (most recent call last):
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_checkers.py", line 181, in _create_violation_error
    msg = icontract._represent.generate_message(contract=contract, resolved_kwargs=resolved_kwargs)
          │                                              │                         └ {'_ARGS': (array([0, 1, 2, 3, 4]),), '_KWARGS': {}, 'x': array([0, 1, 2, 3, 4])}
          │                                              └ <icontract._types.Contract object at 0x7faa09450310>
          └ <module 'icontract' from '/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/__init__.py'>
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_represent.py", line 542, in generate_message
    repr_vals = repr_values(
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_represent.py", line 463, in repr_values
    recompute_visitor.visit(node=lambda_inspection.node.body)
    │                            └ <icontract._represent.ConditionLambdaInspection object at 0x7fa9d4475820>
    └ <icontract._recompute.Visitor object at 0x7fa9d4475c10>
  File "/home/derek/.dotfiles/submodules/pyenv/versions/3.9.5/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
           │       └ <ast.Compare object at 0x7fa9d4475b80>
           └ <bound method Visitor.visit_Compare of <icontract._recompute.Visitor object at 0x7fa9d4475c10>>
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_recompute.py", line 502, in visit_Compare
    left = self.visit(node=node.left)
           │               └ <ast.Compare object at 0x7fa9d4475b80>
           └ <icontract._recompute.Visitor object at 0x7fa9d4475c10>
  File "/home/derek/.dotfiles/submodules/pyenv/versions/3.9.5/lib/python3.9/ast.py", line 407, in visit
    return visitor(node)
           │       └ <ast.Call object at 0x7fa9d4475a90>
           └ <bound method Visitor.visit_Call of <icontract._recompute.Visitor object at 0x7fa9d4475c10>>
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_recompute.py", line 593, in visit_Call
    if PLACEHOLDER in args or PLACEHOLDER in kwargs or PLACEHOLDER in kwargs.values():
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/derek/work/stat-arb/foo.py", line 10, in <module>
    identity(arange(5))
    │        └ <built-in function arange>
    └ <function identity at 0x7fa9d44045e0>
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_checkers.py", line 628, in wrapper
    violation_error = _assert_preconditions(
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_checkers.py", line 290, in _assert_preconditions
    exception = _create_violation_error(contract=contract, resolved_kwargs=resolved_kwargs)
    │           │                                │                         └ {'_ARGS': (array([0, 1, 2, 3, 4]),), '_KWARGS': {}, 'x': array([0, 1, 2, 3, 4])}
    │           │                                └ <icontract._types.Contract object at 0x7faa09450310>
    │           └ <function _create_violation_error at 0x7faa09123550>
    └ None
  File "/home/derek/work/stat-arb/.venv/lib/python3.9/site-packages/icontract/_checkers.py", line 192, in _create_violation_error
    raise RuntimeError(''.join(parts)) from err
                               └ ['Failed to recompute the values of the contract condition:\n', 'File /home/derek/work/stat-arb/foo.py, line 5 in <module>:\n', ...
RuntimeError: Failed to recompute the values of the contract condition:
File /home/derek/work/stat-arb/foo.py, line 5 in <module>:
lambda x: len(x) >= 10

@leshchenko1979
Copy link
Author

I'm trying to isolate this even further:

from icontract import require, ViolationError
from numpy import arange


@require(lambda x: len(x) >= 10)
def identity(x):
    return x


for x in [
    [0, 1, 2, 3, 4],
    (0, 1, 2, 3, 4),
    range(5),
    arange(5),
    arange(10),
]:
    print("Trying", x)
    try:
        identity (x)
        print("Passes")
    except (ViolationError, RuntimeError) as e:
        print(type(e))
    print()

Output:

Trying [0, 1, 2, 3, 4]
<class 'icontract.errors.ViolationError'>

Trying (0, 1, 2, 3, 4)
<class 'icontract.errors.ViolationError'>

Trying range(0, 5)
<class 'icontract.errors.ViolationError'>

Trying [0 1 2 3 4]
<class 'RuntimeError'>

Trying [0 1 2 3 4 5 6 7 8 9]
Passes

It seems like the bug happens when there is an invalid condition based on a numpy array (as it also was with my original case above).

PS. @dycw You have a peculiar yet very informative traceback structure. Where does it come from?

@dycw
Copy link

dycw commented Sep 16, 2021

Hi @leshchenko1979 I use https://github.com/Qix-/better-exceptions

It appears we've both run into this problem as we're in the numpy/pandas domain.

mristin added a commit that referenced this issue Sep 23, 2021
The numpy arrays do not like `PLACEHOLDER in arr` which will cause a
`ValueError`. However, we tested for `PLACEHOLDER` everywhere using
`in` operator in the representation module.

This patch fixes the problem by using explicit `any(...)` and checking
also explicitly for `... is PLACEHOLDER`. In contrast, `in` operator
uses equality (`==`) instead of identity (`is`), which was not the
desirable behavior anyhow originally.

Fixes #229.
@mristin
Copy link
Collaborator

mristin commented Sep 23, 2021

Hi @dycw and @leshchenko1979 ,
Please apologize for the delay. I finally got some time to look into the issue and fix it: #232.

Could you please double-check that the pull request #232 fixes the issue on your machine as well before I merge it in?

mristin added a commit that referenced this issue Sep 23, 2021
The numpy arrays do not like `PLACEHOLDER in arr` which will cause a
`ValueError`. However, we tested for `PLACEHOLDER` everywhere using
`in` operator in the representation module.

This patch fixes the problem by using explicit `any(...)` and checking
also explicitly for `... is PLACEHOLDER`. In contrast, `in` operator
uses equality (`==`) instead of identity (`is`), which was not the
desirable behavior anyhow originally.

Fixes #229.
@leshchenko1979
Copy link
Author

Yep, works fine for me, thanks!

mristin added a commit that referenced this issue Sep 26, 2021
The numpy arrays do not like `PLACEHOLDER in arr` which will cause a
`ValueError`. However, we tested for `PLACEHOLDER` everywhere using
`in` operator in the representation module.

This patch fixes the problem by using explicit `any(...)` and checking
also explicitly for `... is PLACEHOLDER`. In contrast, `in` operator
uses equality (`==`) instead of identity (`is`), which was not the
desirable behavior anyhow originally.

Fixes #229.
@mristin
Copy link
Collaborator

mristin commented Sep 26, 2021

Thanks again for the bug report, @leshchenko1979 and @dycw! I've just released v2.5.5 which should be available on PyPI shortly. Please let me know if there are more issues.

(Btw., could you please star the project on GitHub if you haven't done so already to give it some visibility?)

@dycw
Copy link

dycw commented Sep 26, 2021

Of course, already did!

Thanks for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants