@@ -110,25 +110,21 @@ All field classes accept this set of arguments:
110110 intead of relying on param labels. *
111111
112112* **source ** *(str, optional): * name of internal object key/attribute
113- that will be passed to field's on ``.to_representation(value) `` call.
114- Special ``'*' `` value is allowed that will pass whole object to
115- field when making representation. If not set then default source will
116- be a field name used as a serializer's attribute.
113+ that will be passed to field's on ``.to_representation(value) `` call. If not
114+ set then default source is a field name used as a serializer's attribute.
117115
118116* **validators ** *(list, optional): * list of validator callables.
119117
120118* **many ** *(bool, optional) * set to True if field is in fact a list
121119 of given type objects
122120
123121
124- .. note ::
125-
126- ``source='*' `` is in fact a dirty workaround and will not work well
127- on validation when new object instances needs to be created/updated
128- using POST/PUT requests. This works quite well with simple retrieve/list
129- type resources but in more sophisticated cases it is better to use
130- custom object properties as sources to encapsulate such fields.
122+ .. versionchanged :: 1.0.0
131123
124+ Fields no no longer have special case treatment for ``source='*' `` argument.
125+ If you want to access multiple object keys and values within single
126+ serializer field please refer to :ref: `guide-field-attribute-access ` section
127+ of this document.
132128
133129.. _field-validation :
134130
@@ -143,9 +139,9 @@ in order to provide correct HTTP responses each validator shoud raise
143139.. note ::
144140
145141 Concept of validation for fields is understood here as a process of checking
146- if data of valid type (successfully parsed/processed by
147- ``.from_representation `` handler) does meet some other constraints
148- (lenght, bounds, unique , etc).
142+ if data of valid type (i.e. data that was successfully parsed/processed by
143+ ``.from_representation() `` handler) does meet some other constraints
144+ (lenght, bounds, uniquess , etc).
149145
150146
151147Example of simple validator usage:
@@ -174,31 +170,53 @@ Resource validation
174170~~~~~~~~~~~~~~~~~~~
175171
176172In most cases field level validation is all that you need but sometimes you
177- need to perfom obejct level validation that needs to access multiple fields
178- that are already deserialized and validated. Suggested way to do this in
179- graceful is to override serializer's ``.validate() `` method and raise
180- :class: `graceful.errors.ValidationError ` when your validation fails. This
181- exception will be then automatically translated to HTTP Bad Request response
182- on resource-level handlers. Here is example:
173+ need to perfom validation on whole resource representation or deserialized
174+ object. It is possible to access multiple fields that were already deserialized
175+ and pre-validated directly from serializer class.
176+
177+ You can provide your own object-level serialization handler using serializer's
178+ ``validate() `` method. This method accepts two arguments:
179+
180+ * **object_dict ** *(dict): * it is deserialized object dictionary that already
181+ passed validation. Field sources instead of their representation names are
182+ used as its keys.
183+
184+ * **partial ** *(bool): * it is set to ``True `` only on partial object updates
185+ (e.g. on ``PATCH `` requests). If you plan to support partial resource
186+ modification you should check this field and verify if you object has
187+ all the existing keys.
188+
189+ If your validation fails you should raise the
190+ :class: `graceful.errors.ValidationError ` exception. Following is the example
191+ of resource serializer with custom object-level validation:
183192
184193
185194.. code-block :: python
186195
187196 class DrinkSerializer ():
188- alcohol = StringField(" main ingredient" , required = True )
189- mixed_with = StringField(" what makes it tasty" , required = True )
197+ alcohol = StringField(" main ingredient" )
198+ mixed_with = StringField(" what makes it tasty" )
199+
200+ def validate (self , object_dict , partial ):
201+ # note: always make sure to call super `validate_object()`
202+ # to make sure that per-field validation is enabled.
190203
191- def validate (self , object_dict , partial = False ):
192- # note: always make sure to call super `validate()`
193- # so whole validation of fields works as expected
194- super ().validate(object_dict, partial)
204+ if partial and any ([
205+ ' alcohol' in object_dict,
206+ ' mixed_with' in object_dict,
207+ ]):
208+ raise ValidationError(
209+ " bartender refused to change ingredients"
210+ )
195211
196212 # here is a place for your own validation
197213 if (
198214 object_dict[' alcohol' ] == ' whisky' and
199215 object_dict[' mixed_with' ] == ' cola'
200216 ):
201- raise ValidationError(" bartender refused!')
217+ raise ValidationError(
218+ " bartender refused to mix whisky with cola!"
219+ )
202220
203221
204222 Custom fields
@@ -228,3 +246,88 @@ as a serialized JSON string that we would like to (de)serialize:
228246 def to_representation (data ):
229247 return json.loads(data)
230248
249+ .. _guide-field-attribute-access :
250+
251+
252+ Accessing multiple fields at once
253+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
254+
255+ Sometimes you need to access multiple fields of internal object instance at
256+ once in order to properly represent data in your API. This is very common when
257+ interacting with legacy services/components that cannot be changed or when
258+ your storage engine simply does not allow to store nested or structured objects.
259+
260+ Serializers generally work on per-field basis and allow only to translate field
261+ names between representation and application internal objects. In order to
262+ manipulate multiple representation or internal object instance keys within the
263+ single field you need to create custom field class and override one or more
264+ of following methods:
265+
266+ * ``read_instance(self, instance, key_or_attribute) ``: read value from the
267+ object instance before serialization. The return value will be later passed
268+ as an argument to ``to_representation() `` method. The ``key_or_attribute ``
269+ argument is field's name or source (if ``source `` explicitly specified).
270+ Base implementation defaults to dictionary key lookup or object attribute
271+ lookup.
272+ * ``read_representation(self, representation, key_or_attribute) ``: read value
273+ from the object instance before deserialization. The return value will be
274+ later passed as an argument to ``from_representation() `` method. The
275+ ``key_or_attribute `` argument the field's name. Base implementation defaults
276+ to dictionary key lookup or object attribute lookup.
277+ * ``update_instance(self, instance, key_or_attribute, value) ``: update the
278+ content of object instance after deserialization. The ``value `` argument is
279+ the return value of ``from_representation() `` method. The
280+ ``key_or_attribute `` argument the field's name or source (if ``source ``
281+ explicitly specified). Base implementation defaults to dictionary key
282+ assignment or object attribute assignment.
283+ * ``update_representation(self, representation, key_or_attribute, value) ``:
284+ update the content of representation instance after serialization.
285+ The ``value `` argument is the return value of ``to_representation() `` method.
286+ The ``key_or_attribute `` argument the field's name. Base implementation
287+ defaults to dictionary key assignment or object attribute assignment.
288+
289+ To better explain how to use these methods let's assume that due to some
290+ storage backend constraints we cannot save nested dictionaries. All of fields
291+ of some nested object will have to be stored under separate keys but we still
292+ want to present this to the user as separate nested dictionary. And of course
293+ we want to support both writes and saves.
294+
295+ .. code-block :: python
296+
297+ class OwnerField (RawField ):
298+ def from_representation (self , data ):
299+ if not isinstance (data, dict ):
300+ raise ValueError (" expected object" )
301+
302+ return {
303+ ' owner_name' : data.get(' name' ),
304+ ' owner_age' : data.get(' age' ),
305+ }
306+
307+ def to_representation (self , value ):
308+ return {
309+ ' age' : value.get(' owner_age' ),
310+ ' name' : value.get(' owner_name' ),
311+ }
312+
313+ def validate (self , value ):
314+ print (value)
315+ if ' owner_age' not in value or not isinstance (value[' owner_age' ], int ):
316+ raise ValidationError(" invalid owner age" )
317+
318+ if ' owner_name' not in value:
319+ raise ValidationError(" invalid owner name" )
320+
321+ def update_instance (self , instance , attribute_or_key , value ):
322+ # we assume that instance is always a dictionary so we can
323+ # use the .update() method
324+ instance.update(value)
325+
326+ def read_instance (self , instance , attribute_or_key ):
327+ # .to_representation() method requires acces to whole object
328+ # dictionary so we have to return whole object.
329+ return instance
330+
331+
332+ Similar approach may be used to flatten nested objects into more compact
333+ representations.
0 commit comments