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
Fixed #25370 - Helpful exception message for lamda #13582
Conversation
Lambda expressions can't be serialized. Make the exception message more helpful when it is attempted.
7ec32b1
to
3623f6c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@craigiansmith Thanks for this patch 👍
django/db/migrations/serializer.py
Outdated
msg = 'During serialization of the field' | ||
e.args = ('{} {} the following blew up: {}'.format(msg, self.value, e.args[0]), ) + e.args[1:] | ||
raise e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a reason for modifying a source exception, you can use:
raise ValueError(...) from e
Also blew up
is not a appropriate wording and a new exception is not more informative because it refers to the field class ... the field <django.db.models.fields.CharField>
instead of <app_label>.<model_name>.<field_name>
, maybe:
raise ValueError('Error during %s serializing: %s' % (field, e)) from e
I don't have a quick answer how to get a field path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixxm thanks for the feedback. I have changed the error message as you suggested. But I have kept the .format
notation instead of %s
, if that should also change, please let me know. I will look into how to get the field path before I update with a new commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixxm self.value
shows the field path in the <app label>.<model name>.<field name>
form. I have tested this by starting a django project using the amended source code and attempting to makemigrations for an app with a model that has a field that uses a lambda expression.
I have had trouble getting the test to verify this behavior though, since when I try to import a model from migrations.models
and serialize it, the field is treated as a DeferredAttribute
instead of a model field and so doesn't get handled by the ModelFieldSerializer
. When I tested in a new project I was able to get the desired behavior by changing a field on a model that already had migrations, so the only thing that had to be serialized was the field which I amended to use a lambda expression as its default. At this stage, I don't see how to achieve that in a test. Any suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By making a custom field the test error message shows the desired field path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it's not, without this patch it raises, e.g.
ValueError: Cannot serialize function: lambda
with this patch:
ValueError: Error during <django.db.models.fields.IntegerField> serializing: Cannot serialize function: lambda
It's not more descriptive, IMO. You can have hundreds of IntegerField
s in dozens of apps. I would expect:
ValueError: Error during 'test_app.models.MyModel.field_1.default' serializing: Cannot serialize function: lambda
I don't see much value in this change if it's not feasible to get <app label>.<model name>.<field name>.<parameter>
or at least <app label>.<model name>.<field name>
.
Maybe we should fix this in FunctionTypeSerializer
, it should be doable to get at least test_app.models.MyModel
from __qualname__
and __module__
🕵️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FunctionTypeSerializer
has the same issue, if the field isn't part of a model.
I need to figure out how to add the UnserializableField
to a migrated model, within a test. So that the field is a member of a model, and that the model appears as though it is already migrated, and that the only change is to add the UnserializableField
. Any ideas on how to do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felixxm the latest commit seems to have gotten the desired result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry but it's still the same 🤷 . You can manipulate with tests to create an unreachable state where you will get an expected message but that's not the correct solution.
Please check an attached project and try to run:
$> python manage.py makemigrations
it raises:
ValueError: Error during <django.db.models.fields.IntegerField> serializing: Cannot serialize function: lambda
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @felixxm, you were right, the error message can be constructed in the FunctionTypeSerializer
- but I had trouble getting the field name, any ideas on that one? BTW thanks for the welcome aboard message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for updates 👍
... but I had trouble getting the field name, any ideas on that one?
Unfortunately not, moreover I'm afraid that we will not be able to get <app label>.<model name>.<field name>
or even <app label>.<model name>
in a reliable way. We serialize field
from django.db.models
not a model attribute, that's why it's complicated or even not feasible. Each approach doesn't work in some cases, e.g. constructing messages in the FunctionTypeSerializer
will not work for lambda
s defined in the module:
Error during serializing test_one.models.<lambda>: ...
or imported from other modules:
ValueError: Error during serializing test_one.utils.<lambda>: ...
I think we should close this as wontfix 😞
- Changed error message for lambda serialization - Confirmed that error message shows field module path
tests/migrations/test_writer.py
Outdated
@@ -252,9 +252,10 @@ def test_serialize_constants(self): | |||
self.assertSerializedEqual(False) | |||
|
|||
def test_trying_to_serialize_a_lambda_in_field_argument_should_raise_helpful_message(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A test name over 79 characters seems too verbose to me.
Maybe: test_serialize_field_with_lambda
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion. I will update it.
- Change test name to make it more succinct - Use a custom field class to demonstrate correct field path in test
django/db/migrations/serializer.py
Outdated
try: | ||
return self.serialize_deconstructed(path, args, kwargs) | ||
except ValueError as e: | ||
raise ValueError('Error during {} serializing: {}'.format(self.value, e)) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guidance is to try and avoid format. Do you think this could be more readable written as an f string or % formatting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, you're right. It's better to use an f string.
tests/migrations/test_writer.py
Outdated
def test_serialize_field_with_lambda(self): | ||
field = UnserializableField() | ||
|
||
error = 'Cannot serialize function: lambda' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion, write this as one string in full?
msg = (
"Error during...."
" ....."
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By using an f string it was easy to fit it all on one line without exceeding the character limit.
- Use f string instead of string.format - Use a single string for test message
Test shows that the error message states app_label.model.field
Construct error message in FunctionTypeSerializer instead of ModelFieldSerializer. Remove migration for the associated test. Rename the model used to test this.
@craigiansmith Thanks for your efforts! 🥇 Unfortunately, IMO it's not doable. Closing per ticket. |
Lambda expressions can't be serialized. Make the exception message more
helpful when a lamda is used as an argument to a model field.