Skip to content

Commit

Permalink
Make set_value a method within Serializer (#8001)
Browse files Browse the repository at this point in the history
* Make set_value a static method for Serializers

As an alternative to #7671, let the method be overridden if needed. As
the function is only used for serializers, it has a better place in the
Serializer class.

* Set `set_value` as an object (non-static) method

* Add tests for set_value()

These tests follow the examples given in the method.
  • Loading branch information
tienne-B authored May 24, 2023
1 parent a25aac7 commit d252d22
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 23 deletions.
21 changes: 0 additions & 21 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,27 +113,6 @@ def get_attribute(instance, attrs):
return instance


def set_value(dictionary, keys, value):
"""
Similar to Python's built in `dictionary[key] = value`,
but takes a list of nested keys instead of a single key.
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
"""
if not keys:
dictionary.update(value)
return

for key in keys[:-1]:
if key not in dictionary:
dictionary[key] = {}
dictionary = dictionary[key]

dictionary[keys[-1]] = value


def to_choices_dict(choices):
"""
Convert choices into key/value dicts.
Expand Down
24 changes: 22 additions & 2 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

from rest_framework.compat import postgres_fields
from rest_framework.exceptions import ErrorDetail, ValidationError
from rest_framework.fields import get_error_detail, set_value
from rest_framework.fields import get_error_detail
from rest_framework.settings import api_settings
from rest_framework.utils import html, model_meta, representation
from rest_framework.utils.field_mapping import (
Expand Down Expand Up @@ -346,6 +346,26 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.')
}

def set_value(self, dictionary, keys, value):
"""
Similar to Python's built in `dictionary[key] = value`,
but takes a list of nested keys instead of a single key.
set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2}
set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2}
set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}}
"""
if not keys:
dictionary.update(value)
return

for key in keys[:-1]:
if key not in dictionary:
dictionary[key] = {}
dictionary = dictionary[key]

dictionary[keys[-1]] = value

@cached_property
def fields(self):
"""
Expand Down Expand Up @@ -492,7 +512,7 @@ def to_internal_value(self, data):
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
self.set_value(ret, field.source_attrs, validated_value)

if errors:
raise ValidationError(errors)
Expand Down
21 changes: 21 additions & 0 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,24 @@ class TestSerializer(serializers.Serializer):

assert (s.data | {}).__class__ == s.data.__class__
assert ({} | s.data).__class__ == s.data.__class__


class TestSetValueMethod:
# Serializer.set_value() modifies the first parameter in-place.

s = serializers.Serializer()

def test_no_keys(self):
ret = {'a': 1}
self.s.set_value(ret, [], {'b': 2})
assert ret == {'a': 1, 'b': 2}

def test_one_key(self):
ret = {'a': 1}
self.s.set_value(ret, ['x'], 2)
assert ret == {'a': 1, 'x': 2}

def test_nested_key(self):
ret = {'a': 1}
self.s.set_value(ret, ['x', 'y'], 2)
assert ret == {'a': 1, 'x': {'y': 2}}

0 comments on commit d252d22

Please sign in to comment.