fix: Handle unhashable BooleanField representation value#9973
Conversation
There was a problem hiding this comment.
Pull request overview
This PR makes BooleanField.to_representation() resilient to unhashable values (e.g., [], {}, set()) by suppressing TypeError during membership checks, aligning its behavior with to_internal_value() while preserving the existing bool(value) fallback.
Changes:
- Wrap
BooleanField.to_representation()’s TRUE/FALSE/NULL token membership checks incontextlib.suppress(TypeError). - Update
BooleanFieldoutput tests to use a list of(value, expected)pairs (allowing unhashable test inputs) and add coverage for unhashable collections.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
rest_framework/fields.py |
Suppresses TypeError during set-membership checks in BooleanField.to_representation() so unhashable values fall back to bool(value). |
tests/test_fields.py |
Extends BooleanField representation coverage to include unhashable collections by switching outputs to tuple-pairs and adding new cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
PR seems reasonable and appropriate. Only a potential edge-case scenario might be cause for concern; Let's say that, either by mistake or due to a custom library integration, a complex custom object that cannot be converted to a string or whose type cannot be checked was passed to the Old Behavior: The code raised a New Behavior: The code does not crash; instead, it passes the object through the However, this is simply a bug fix that needed to be addressed anyway. That’s why I think it’s acceptable. |
Taking a step back away from the model field itself, what concrete example would cause that from being used? I'm thinking of a minimal example of a serializer with the data/Python object that would make use of this. I think we would need to map a complex field (e.g. dict) to a |
Yes, actually, this is more of an improvement aimed at preventing the “representation” step from crashing. As it stands now, the system crashes when dealing with a complex variable type because there is no mechanism to handle this situation. This code prevents that error and manages this part using a boolean. |
|
Yes, I got that, I'm more wondering what would be a genuine case where someone would need that behaviour. It feels like I would only use that by mistake, if I mapped a complex field to be serialized as a boolean... But there might be a good use case for it, in which case the docs could be worth updating? |
|
Thanks for the question. I agree that this is not a common case when a BooleanField is mapped directly to a normal model BooleanField. The concrete case I had in mind is when BooleanField is used against a non-model attribute, custom property, annotation-like value, or external data source where the value may not be strictly boolean but is still intended to be represented by truthiness. For example: With the current implementation, serializing this object can raise TypeError because dict is unhashable during the TRUE/FALSE/NULL membership checks. However, BooleanField already has a final bool(value) fallback, so this change lets unrecognized unhashable values reach that existing fallback instead of crashing. I agree this should not encourage mapping arbitrary complex objects to BooleanField. The intention is only to make to_representation consistent with the existing fallback behavior and with the TypeError handling already present in to_internal_value(). |
|
To clarify the bug aspect: BooleanField.to_representation() already has a generic bool(value) fallback for values that are not in TRUE_VALUES, FALSE_VALUES, or NULL_VALUES. For hashable unknown values, that fallback is reached. For unhashable unknown values, a TypeError is raised before the fallback is reached. So this change does not introduce new behavior; it only makes unhashable values follow the same fallback path that hashable values already use. |
browniebroke
left a comment
There was a problem hiding this comment.
I had more time today to check the docs, and think it's fine as is.
This is a small consistency fix for
BooleanField.to_representation(), matching the existingTypeErrorhandling used byto_internal_value()while preserving the existingbool(value)fallback.Summary
BooleanField.to_representation()now handles unhashable values consistently withto_internal_value().Previously, values like
[],{}, orset()could raiseTypeErrorduring membership checks before reaching the existingbool(value)fallback. This change suppressesTypeErroraround the explicit true/false/null token checks, allowing unrecognized unhashable values to be represented by their truthiness.Test
uv run --no-project --with 'django==5.2.*' --with 'pytest==9.*' --with pytest-django --with dj-database-url --with importlib-metadata --with pytz -m pytest tests/test_fields.py -k BooleanField