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