From 17fb350d3f05bae1370cd2ae699318f5e741f1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Beaul=C3=A9?= Date: Thu, 20 May 2021 12:04:08 -0400 Subject: [PATCH 1/3] 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. --- rest_framework/fields.py | 21 --------------------- rest_framework/serializers.py | 25 +++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index e4be54751d..3a6294e121 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -109,27 +109,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. diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 49eec82591..d8226085b5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -26,7 +26,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 ( @@ -337,6 +337,27 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass): 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } + @staticmethod + 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 + @cached_property def fields(self): """ @@ -483,7 +504,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) From dcbdb7158ca9258601614a44eef3fd408f9578a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Beaul=C3=A9?= Date: Wed, 19 Oct 2022 19:13:44 -0300 Subject: [PATCH 2/3] Set `set_value` as an object (non-static) method --- rest_framework/serializers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index d8226085b5..632b886317 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -337,8 +337,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass): 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } - @staticmethod - def set_value(dictionary, keys, value): + 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. From a487f85d2d1a7b70bd313df0fc4aa685f3d2e5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Beaul=C3=A9?= Date: Mon, 27 Feb 2023 21:40:33 -0400 Subject: [PATCH 3/3] Add tests for set_value() These tests follow the examples given in the method. --- tests/test_serializer.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index afefd70e1c..13e768a34c 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -740,3 +740,24 @@ class TestSerializer(A, B): 'f4': serializers.CharField, 'f5': serializers.CharField, } + + +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}}