44# Copyright (c) 2021, 2023 Oracle and/or its affiliates.
55# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
66
7+ """
8+ This module provides a base class for serializable items, as well as methods for serializing and
9+ deserializing objects to and from JSON and YAML formats. It also includes methods for reading and
10+ writing serialized objects to and from files.
11+ """
12+
713import dataclasses
814import json
915from abc import ABC , abstractmethod
@@ -271,11 +277,16 @@ def from_yaml(
271277
272278 Parameters
273279 ----------
274- yaml_string (string, optional): YAML string. Defaults to None.
275- uri (string, optional): URI location of file containing YAML string. Defaults to None.
276- loader (callable, optional): Custom YAML loader. Defaults to CLoader/SafeLoader.
277- kwargs (dict): keyword arguments to be passed into fsspec.open(). For OCI object storage, this should be config="path/to/.oci/config".
278- For other storage connections consider e.g. host, port, username, password, etc.
280+ yaml_string (string, optional)
281+ YAML string. Defaults to None.
282+ uri (string, optional)
283+ URI location of file containing YAML string. Defaults to None.
284+ loader (callable, optional)
285+ Custom YAML loader. Defaults to CLoader/SafeLoader.
286+ kwargs (dict)
287+ keyword arguments to be passed into fsspec.open().
288+ For OCI object storage, this should be config="path/to/.oci/config".
289+ For other storage connections consider e.g. host, port, username, password, etc.
279290
280291 Raises
281292 ------
@@ -288,10 +299,10 @@ def from_yaml(
288299 Returns instance of the class
289300 """
290301 if yaml_string :
291- return cls .from_dict (yaml .load (yaml_string , Loader = loader ))
302+ return cls .from_dict (yaml .load (yaml_string , Loader = loader ), ** kwargs )
292303 if uri :
293304 yaml_dict = yaml .load (cls ._read_from_file (uri = uri , ** kwargs ), Loader = loader )
294- return cls .from_dict (yaml_dict )
305+ return cls .from_dict (yaml_dict , ** kwargs )
295306 raise ValueError ("Must provide either YAML string or URI location" )
296307
297308 @classmethod
@@ -345,8 +356,8 @@ class DataClassSerializable(Serializable):
345356 Returns an instance of the class instantiated from the dictionary provided.
346357 """
347358
348- @staticmethod
349- def _validate_dict (obj_dict : Dict ) -> bool :
359+ @classmethod
360+ def _validate_dict (cls , obj_dict : Dict ) -> bool :
350361 """validate the dictionary.
351362
352363 Parameters
@@ -379,7 +390,7 @@ def to_dict(self, **kwargs) -> Dict:
379390 obj_dict = dataclasses .asdict (self )
380391 if "side_effect" in kwargs and kwargs ["side_effect" ]:
381392 obj_dict = DataClassSerializable ._normalize_dict (
382- obj_dict = obj_dict , case = kwargs ["side_effect" ]
393+ obj_dict = obj_dict , case = kwargs ["side_effect" ], recursively = True
383394 )
384395 return obj_dict
385396
@@ -388,6 +399,8 @@ def from_dict(
388399 cls ,
389400 obj_dict : dict ,
390401 side_effect : Optional [SideEffect ] = SideEffect .CONVERT_KEYS_TO_LOWER .value ,
402+ ignore_unknown : Optional [bool ] = False ,
403+ ** kwargs ,
391404 ) -> "DataClassSerializable" :
392405 """Returns an instance of the class instantiated by the dictionary provided.
393406
@@ -399,6 +412,8 @@ def from_dict(
399412 side effect to take on the dictionary. The side effect can be either
400413 convert the dictionary keys to "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
401414 or "upper"(SideEffect.CONVERT_KEYS_TO_UPPER.value) cases.
415+ ignore_unknown: (bool, optional). Defaults to `False`.
416+ Whether to ignore unknown fields or not.
402417
403418 Returns
404419 -------
@@ -415,25 +430,36 @@ def from_dict(
415430
416431 allowed_fields = set ([f .name for f in dataclasses .fields (cls )])
417432 wrong_fields = set (obj_dict .keys ()) - allowed_fields
418- if wrong_fields :
433+ if wrong_fields and not ignore_unknown :
419434 logger .warning (
420435 f"The class { cls .__name__ } doesn't contain attributes: `{ list (wrong_fields )} `. "
421436 "These fields will be ignored."
422437 )
423438
424- obj = cls (** {key : obj_dict [ key ] for key in allowed_fields })
439+ obj = cls (** {key : obj_dict . get ( key ) for key in allowed_fields })
425440
426441 for key , value in obj_dict .items ():
427- if isinstance (value , dict ) and hasattr (
428- getattr (cls (), key ).__class__ , "from_dict"
442+ if (
443+ key in allowed_fields
444+ and isinstance (value , dict )
445+ and hasattr (getattr (cls (), key ).__class__ , "from_dict" )
429446 ):
430- attribute = getattr (cls (), key ).__class__ .from_dict (value )
447+ attribute = getattr (cls (), key ).__class__ .from_dict (
448+ value ,
449+ ignore_unknown = ignore_unknown ,
450+ side_effect = side_effect ,
451+ ** kwargs ,
452+ )
431453 setattr (obj , key , attribute )
454+
432455 return obj
433456
434457 @staticmethod
435458 def _normalize_dict (
436- obj_dict : Dict , case : str = SideEffect .CONVERT_KEYS_TO_LOWER .value
459+ obj_dict : Dict ,
460+ recursively : bool = False ,
461+ case : str = SideEffect .CONVERT_KEYS_TO_LOWER .value ,
462+ ** kwargs ,
437463 ) -> Dict :
438464 """lower all the keys.
439465
@@ -444,6 +470,8 @@ def _normalize_dict(
444470 case: (optional, str). Defaults to "lower".
445471 the case to normalized to. can be either "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
446472 or "upper"(SideEffect.CONVERT_KEYS_TO_UPPER.value).
473+ recursively: (bool, optional). Defaults to `False`.
474+ Whether to recursively normalize the dictionary or not.
447475
448476 Returns
449477 -------
@@ -452,12 +480,16 @@ def _normalize_dict(
452480 """
453481 normalized_obj_dict = {}
454482 for key , value in obj_dict .items ():
455- if isinstance (value , dict ):
483+ if recursively and isinstance (value , dict ):
456484 value = DataClassSerializable ._normalize_dict (
457- value , case = SideEffect . CONVERT_KEYS_TO_UPPER . value
485+ value , case = case , recursively = recursively , ** kwargs
458486 )
459487 normalized_obj_dict = DataClassSerializable ._normalize_key (
460- normalized_obj_dict = normalized_obj_dict , key = key , value = value , case = case
488+ normalized_obj_dict = normalized_obj_dict ,
489+ key = key ,
490+ value = value ,
491+ case = case ,
492+ ** kwargs ,
461493 )
462494 return normalized_obj_dict
463495
@@ -467,7 +499,7 @@ def _normalize_key(
467499 ) -> Dict :
468500 """helper function to normalize the key in the case specified and add it back to the dictionary.
469501
470- Paramaters
502+ Parameters
471503 ----------
472504 normalized_obj_dict: (Dict)
473505 the dictionary to append the key and value to.
@@ -476,17 +508,18 @@ def _normalize_key(
476508 value: (Union[str, Dict])
477509 value to be added.
478510 case: (str)
479- the case to normalized to. can be either "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
511+ The case to normalized to. can be either "lower" (SideEffect.CONVERT_KEYS_TO_LOWER.value)
480512 or "upper"(SideEffect.CONVERT_KEYS_TO_UPPER.value).
481513
482514 Raises
483515 ------
484- NotImplementedError: if case provided is not either "lower" or "upper".
516+ NotImplementedError
517+ Raised when `case` is not supported.
485518
486519 Returns
487520 -------
488521 Dict
489- normalized dictionary with the key and value added in the case specified.
522+ Normalized dictionary with the key and value added in the case specified.
490523 """
491524 if case .lower () == SideEffect .CONVERT_KEYS_TO_LOWER .value :
492525 normalized_obj_dict [key .lower ()] = value
0 commit comments