Skip to content

Commit

Permalink
Fixed crash when specifying Field.parsed_data callback and the field …
Browse files Browse the repository at this point in the history
…was not editable. Also improved error messages for evaluation.
  • Loading branch information
boxed committed Jan 17, 2025
1 parent b0a16ed commit a2690b8
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 19 deletions.
21 changes: 13 additions & 8 deletions iommi/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ def get_callable_description(c):
return 'lambda found at: `{}`'.format(inspect.getsource(c).strip())
except OSError: # pragma: no cover
pass
return f'`{c}`'
if isinstance(c, Namespace):
return f'`{c}`'
return f'{c.__module__}.{c.__name__}'


def is_callable(v):
Expand All @@ -71,14 +73,17 @@ def evaluate(func_or_value, *, __signature=None, __strict=False, __match_empty=T
return func_or_value(**kwargs)

if __strict:
arguments = '\n '.join(keys(kwargs))
parameters = '\n '.join(inspect.getfullargspec(func_or_value)[0])
assert isinstance(func_or_value, Namespace) and 'call_target' not in func_or_value, (
"Evaluating {} didn't resolve it into a value but strict mode was active, "
"the signature doesn't match the given parameters. "
"We had these arguments: {}".format(
get_callable_description(func_or_value),
', '.join(keys(kwargs)),
)
)
f'''Evaluating {get_callable_description(func_or_value)} didn't resolve it into a value but strict mode was active. The signature doesn't match the given parameters.
Possible inputs:
{arguments}
Function inputs:
{parameters}
''')
return func_or_value


Expand Down
40 changes: 34 additions & 6 deletions iommi/evaluate__tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,39 @@ def test_evaluate_strict():
with pytest.raises(AssertionError) as e:
evaluate_strict(lambda foo: 1, bar=2, baz=4)

assert (
str(e.value)
== "Evaluating lambda found at: `evaluate_strict(lambda foo: 1, bar=2, baz=4)` didn't resolve it into a value but strict mode was active, the signature doesn't match the given parameters. We had these arguments: bar, baz"
)
expected = '''
Evaluating lambda found at: `evaluate_strict(lambda foo: 1, bar=2, baz=4)` didn't resolve it into a value but strict mode was active. The signature doesn't match the given parameters.
Possible inputs:
bar
baz
Function inputs:
foo
'''
assert str(e.value).strip() == expected.strip()

assert evaluate_strict(lambda **_: 1, bar=2, baz=4) == 1


def test_evaluate_strict_for_def():
def bar(foo, **_):
return 1

with pytest.raises(AssertionError) as e:
evaluate_strict(bar, bar=2, baz=4)

expected = '''
Evaluating iommi.evaluate__tests.bar didn't resolve it into a value but strict mode was active. The signature doesn't match the given parameters.
Possible inputs:
bar
baz
Function inputs:
foo
'''
assert str(e.value).strip() == expected.strip()

assert evaluate_strict(lambda **_: 1, bar=2, baz=4) == 1

Expand All @@ -193,8 +222,7 @@ def foo(a, b, c, *, bar, **kwargs):
assert False # pragma: no cover

description = get_callable_description(foo)
assert description.startswith('`<function test_get_callable_description.<locals>.foo at')
assert description.endswith('`')
assert description == 'iommi.evaluate__tests.foo'


def test_get_callable_description_nested_lambda():
Expand Down
1 change: 1 addition & 0 deletions iommi/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,7 @@ def bind_from_instance(self):

if not self.editable:
self.value = self.initial
self.parsed_data = MISSING
else:
self._read_raw_data()

Expand Down
10 changes: 10 additions & 0 deletions iommi/form__tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3930,3 +3930,13 @@ def test_required_truthy_bug():
assert form.actions.submit.iommi_name() == 'submit'
assert 'genres' not in form.get_errors()['fields']
assert form.get_errors()['fields']['name'] == {'This field is required'}


def test_parsed_data_does_not_crash_on_non_editable():
Form.create(
auto__model=Album,
fields__name=dict(
editable=False,
parsed_data=lambda **_: 1,
)
).bind(request=req('POST', **{'-submit': ''}))
19 changes: 18 additions & 1 deletion iommi/traversable.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import functools
import inspect
from typing import (
Any,
Dict,
Expand All @@ -10,6 +11,7 @@
evaluate_attrs,
)
from iommi.base import (
keys,
NOT_BOUND_MESSAGE,
items,
)
Expand Down Expand Up @@ -262,7 +264,22 @@ def bind(self, *, parent=None, request=None):
for k in get_special_evaluated_attributes(result):
v = getattr(result, k)
if is_callable(v) and not isinstance(v, type):
assert False, ('SpecialEvaluatedRefinable not evaluated', k, v, repr(result))
arguments = '\n '.join(keys(result.iommi_evaluate_parameters()))
parameters = '\n '.join(inspect.getfullargspec(v)[0])
assert False, f'''SpecialEvaluatedRefinable not evaluated
Refinable name:
{k}
Path:
{result.iommi_dunder_path}
Possible inputs:
{arguments}
Function inputs:
{parameters}
'''

return result

Expand Down
6 changes: 2 additions & 4 deletions iommi/traversable__tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,10 +493,8 @@ def broken_callback(a):
t.invoke_callback(broken_callback)

actual = str(e.value)
assert actual.startswith(
'TypeError when invoking callback `<function test_invoke_callback_error_message_function.<locals>.broken_callback at 0x'
)
assert actual.endswith('`.\nKeyword arguments:\n params\n request\n root\n traversable\n user')
expected = 'TypeError when invoking callback iommi.traversable__tests.broken_callback.\nKeyword arguments:\n params\n request\n root\n traversable\n user'
assert actual == expected


def test_invoke_callback_transparent_type_error():
Expand Down

0 comments on commit a2690b8

Please sign in to comment.