Skip to content

Commit a2690b8

Browse files
committed
Fixed crash when specifying Field.parsed_data callback and the field was not editable. Also improved error messages for evaluation.
1 parent b0a16ed commit a2690b8

File tree

6 files changed

+78
-19
lines changed

6 files changed

+78
-19
lines changed

iommi/evaluate.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ def get_callable_description(c):
5151
return 'lambda found at: `{}`'.format(inspect.getsource(c).strip())
5252
except OSError: # pragma: no cover
5353
pass
54-
return f'`{c}`'
54+
if isinstance(c, Namespace):
55+
return f'`{c}`'
56+
return f'{c.__module__}.{c.__name__}'
5557

5658

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

7375
if __strict:
76+
arguments = '\n '.join(keys(kwargs))
77+
parameters = '\n '.join(inspect.getfullargspec(func_or_value)[0])
7478
assert isinstance(func_or_value, Namespace) and 'call_target' not in func_or_value, (
75-
"Evaluating {} didn't resolve it into a value but strict mode was active, "
76-
"the signature doesn't match the given parameters. "
77-
"We had these arguments: {}".format(
78-
get_callable_description(func_or_value),
79-
', '.join(keys(kwargs)),
80-
)
81-
)
79+
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.
80+
81+
Possible inputs:
82+
{arguments}
83+
84+
Function inputs:
85+
{parameters}
86+
''')
8287
return func_or_value
8388

8489

iommi/evaluate__tests.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,39 @@ def test_evaluate_strict():
171171
with pytest.raises(AssertionError) as e:
172172
evaluate_strict(lambda foo: 1, bar=2, baz=4)
173173

174-
assert (
175-
str(e.value)
176-
== "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"
177-
)
174+
expected = '''
175+
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.
176+
177+
Possible inputs:
178+
bar
179+
baz
180+
181+
Function inputs:
182+
foo
183+
'''
184+
assert str(e.value).strip() == expected.strip()
185+
186+
assert evaluate_strict(lambda **_: 1, bar=2, baz=4) == 1
187+
188+
189+
def test_evaluate_strict_for_def():
190+
def bar(foo, **_):
191+
return 1
192+
193+
with pytest.raises(AssertionError) as e:
194+
evaluate_strict(bar, bar=2, baz=4)
195+
196+
expected = '''
197+
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.
198+
199+
Possible inputs:
200+
bar
201+
baz
202+
203+
Function inputs:
204+
foo
205+
'''
206+
assert str(e.value).strip() == expected.strip()
178207

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

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

195224
description = get_callable_description(foo)
196-
assert description.startswith('`<function test_get_callable_description.<locals>.foo at')
197-
assert description.endswith('`')
225+
assert description == 'iommi.evaluate__tests.foo'
198226

199227

200228
def test_get_callable_description_nested_lambda():

iommi/form.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ def bind_from_instance(self):
883883

884884
if not self.editable:
885885
self.value = self.initial
886+
self.parsed_data = MISSING
886887
else:
887888
self._read_raw_data()
888889

iommi/form__tests.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3930,3 +3930,13 @@ def test_required_truthy_bug():
39303930
assert form.actions.submit.iommi_name() == 'submit'
39313931
assert 'genres' not in form.get_errors()['fields']
39323932
assert form.get_errors()['fields']['name'] == {'This field is required'}
3933+
3934+
3935+
def test_parsed_data_does_not_crash_on_non_editable():
3936+
Form.create(
3937+
auto__model=Album,
3938+
fields__name=dict(
3939+
editable=False,
3940+
parsed_data=lambda **_: 1,
3941+
)
3942+
).bind(request=req('POST', **{'-submit': ''}))

iommi/traversable.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import copy
22
import functools
3+
import inspect
34
from typing import (
45
Any,
56
Dict,
@@ -10,6 +11,7 @@
1011
evaluate_attrs,
1112
)
1213
from iommi.base import (
14+
keys,
1315
NOT_BOUND_MESSAGE,
1416
items,
1517
)
@@ -262,7 +264,22 @@ def bind(self, *, parent=None, request=None):
262264
for k in get_special_evaluated_attributes(result):
263265
v = getattr(result, k)
264266
if is_callable(v) and not isinstance(v, type):
265-
assert False, ('SpecialEvaluatedRefinable not evaluated', k, v, repr(result))
267+
arguments = '\n '.join(keys(result.iommi_evaluate_parameters()))
268+
parameters = '\n '.join(inspect.getfullargspec(v)[0])
269+
assert False, f'''SpecialEvaluatedRefinable not evaluated
270+
271+
Refinable name:
272+
{k}
273+
274+
Path:
275+
{result.iommi_dunder_path}
276+
277+
Possible inputs:
278+
{arguments}
279+
280+
Function inputs:
281+
{parameters}
282+
'''
266283

267284
return result
268285

iommi/traversable__tests.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,10 +493,8 @@ def broken_callback(a):
493493
t.invoke_callback(broken_callback)
494494

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

501499

502500
def test_invoke_callback_transparent_type_error():

0 commit comments

Comments
 (0)