serde.se
This module provides serialize
, is_serializable
to_dict
, to_tuple
and classes
and functions associated with serialization.
1""" 2This module provides `serialize`, `is_serializable` `to_dict`, `to_tuple` and classes 3and functions associated with serialization. 4""" 5 6from __future__ import annotations 7import abc 8import copy 9import dataclasses 10import functools 11import typing 12import itertools 13import jinja2 14from dataclasses import dataclass, is_dataclass 15from typing import TypeVar, Literal, Generic, Optional, Any, Union 16from collections.abc import Callable, Iterable, Iterator 17from beartype import beartype, BeartypeConf 18from beartype.door import is_bearable 19from typing_extensions import dataclass_transform 20 21from .compat import ( 22 SerdeError, 23 SerdeSkip, 24 T, 25 get_origin, 26 is_any, 27 is_bare_dict, 28 is_bare_list, 29 is_bare_opt, 30 is_bare_set, 31 is_bare_tuple, 32 is_class_var, 33 is_datetime, 34 is_datetime_instance, 35 is_dict, 36 is_enum, 37 is_generic, 38 is_list, 39 is_literal, 40 is_none, 41 is_opt, 42 is_primitive, 43 is_set, 44 is_str_serializable, 45 is_str_serializable_instance, 46 is_tuple, 47 is_union, 48 is_variable_tuple, 49 is_pep695_type_alias, 50 iter_types, 51 iter_unions, 52 type_args, 53 typename, 54) 55from .core import ( 56 ClassSerializer, 57 CACHE, 58 SERDE_SCOPE, 59 TO_DICT, 60 TO_ITER, 61 UNION_SE_PREFIX, 62 DefaultTagging, 63 Field, 64 Scope, 65 Tagging, 66 TypeCheck, 67 add_func, 68 coerce_object, 69 disabled, 70 strict, 71 fields, 72 is_instance, 73 logger, 74 raise_unsupported_type, 75 union_func_name, 76 GLOBAL_CLASS_SERIALIZER, 77) 78from .numpy import ( 79 is_numpy_array, 80 is_numpy_jaxtyping, 81 is_numpy_datetime, 82 is_numpy_scalar, 83 serialize_numpy_array, 84 serialize_numpy_datetime, 85 serialize_numpy_scalar, 86) 87 88__all__ = ["serialize", "is_serializable", "to_dict", "to_tuple"] 89 90 91SerializeFunc = Callable[[type[Any], Any], Any] 92""" Interface of Custom serialize function. """ 93 94 95def default_serializer(_cls: type[Any], obj: Any) -> Any: 96 """ 97 Marker function to tell serde to use the default serializer. It's used when custom serializer 98 is specified at the class but you want to override a field with the default serializer. 99 """ 100 101 102def serde_legacy_custom_class_serializer( 103 cls: type[Any], obj: Any, custom: SerializeFunc, default: Callable[[], Any] 104) -> Any: 105 try: 106 return custom(cls, obj) 107 except SerdeSkip: 108 return default() 109 110 111class Serializer(Generic[T], metaclass=abc.ABCMeta): 112 """ 113 `Serializer` base class. Subclass this to customize serialize behaviour. 114 115 See `serde.json.JsonSerializer` and `serde.msgpack.MsgPackSerializer` for example usage. 116 """ 117 118 @classmethod 119 @abc.abstractmethod 120 def serialize(cls, obj: Any, **opts: Any) -> T: 121 raise NotImplementedError 122 123 124def _make_serialize( 125 cls_name: str, 126 fields: Iterable[Union[str, tuple[str, type[Any]], tuple[str, type[Any], Any]]], 127 *args: Any, 128 rename_all: Optional[str] = None, 129 reuse_instances_default: bool = False, 130 convert_sets_default: bool = False, 131 serializer: Optional[SerializeFunc] = None, 132 tagging: Tagging = DefaultTagging, 133 type_check: TypeCheck = disabled, 134 serialize_class_var: bool = False, 135 class_serializer: Optional[ClassSerializer] = None, 136 **kwargs: Any, 137) -> type[Any]: 138 """ 139 Create a serializable class programatically. 140 """ 141 C = dataclasses.make_dataclass(cls_name, fields, *args, **kwargs) 142 C = serialize( 143 C, 144 rename_all=rename_all, 145 reuse_instances_default=reuse_instances_default, 146 convert_sets_default=convert_sets_default, 147 serializer=serializer, 148 tagging=tagging, 149 type_check=type_check, 150 serialize_class_var=serialize_class_var, 151 **kwargs, 152 ) 153 return C 154 155 156# The `serialize` function can call itself recursively when it needs to generate code for 157# unmarked dataclasses. To avoid infinite recursion, this array remembers types for which code is 158# currently being generated. 159GENERATION_STACK = [] 160 161 162@dataclass_transform() 163def serialize( 164 _cls: Optional[type[T]] = None, 165 rename_all: Optional[str] = None, 166 reuse_instances_default: bool = False, 167 convert_sets_default: bool = False, 168 serializer: Optional[SerializeFunc] = None, 169 tagging: Tagging = DefaultTagging, 170 type_check: TypeCheck = strict, 171 serialize_class_var: bool = False, 172 class_serializer: Optional[ClassSerializer] = None, 173 **kwargs: Any, 174) -> type[T]: 175 """ 176 A dataclass with this decorator is serializable into any of the data formats 177 supported by pyserde. 178 179 >>> from datetime import datetime 180 >>> from serde import serialize 181 >>> from serde.json import to_json 182 >>> 183 >>> @serialize 184 ... class Foo: 185 ... i: int 186 ... s: str 187 ... f: float 188 ... b: bool 189 >>> 190 >>> to_json(Foo(i=10, s='foo', f=100.0, b=True)) 191 '{"i":10,"s":"foo","f":100.0,"b":true}' 192 """ 193 194 def wrap(cls: type[T]) -> type[T]: 195 tagging.check() 196 197 # If no `dataclass` found in the class, dataclassify it automatically. 198 if not is_dataclass(cls): 199 dataclass(cls) 200 201 if type_check.is_strict(): 202 serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError)) 203 serde_beartype(cls) 204 205 g: dict[str, Any] = {} 206 207 # Create a scope storage used by serde. 208 # Each class should get own scope. Child classes can not share scope with parent class. 209 # That's why we need the "scope.cls is not cls" check. 210 scope: Optional[Scope] = getattr(cls, SERDE_SCOPE, None) 211 if scope is None or scope.cls is not cls: 212 scope = Scope( 213 cls, 214 reuse_instances_default=reuse_instances_default, 215 convert_sets_default=convert_sets_default, 216 ) 217 setattr(cls, SERDE_SCOPE, scope) 218 219 class_serializers: list[ClassSerializer] = list( 220 itertools.chain(GLOBAL_CLASS_SERIALIZER, [class_serializer] if class_serializer else []) 221 ) 222 223 # Set some globals for all generated functions 224 g["cls"] = cls 225 g["copy"] = copy 226 g["serde_scope"] = scope 227 g["SerdeError"] = SerdeError 228 g["raise_unsupported_type"] = raise_unsupported_type 229 g["enum_value"] = enum_value 230 g["is_dataclass"] = is_dataclass 231 g["typename"] = typename # used in union functions 232 g["is_instance"] = is_instance # used in union functions 233 g["to_obj"] = to_obj 234 g["typing"] = typing 235 g["Literal"] = Literal 236 g["TypeCheck"] = TypeCheck 237 g["disabled"] = disabled 238 g["coerce_object"] = coerce_object 239 g["class_serializers"] = class_serializers 240 if serializer: 241 g["serde_legacy_custom_class_serializer"] = functools.partial( 242 serde_legacy_custom_class_serializer, custom=serializer 243 ) 244 245 # Collect types used in the generated code. 246 for typ in iter_types(cls): 247 # When we encounter a dataclass not marked with serialize, then also generate serialize 248 # functions for it. 249 if is_dataclass_without_se(typ) and typ is not cls: 250 # We call serialize and not wrap to make sure that we will use the default serde 251 # configuration for generating the serialization function. 252 serialize(typ) 253 254 if is_primitive(typ) and not is_enum(typ): 255 continue 256 g[typename(typ)] = typ 257 258 # render all union functions 259 for union in iter_unions(cls): 260 union_args = list(type_args(union)) 261 union_key = union_func_name(UNION_SE_PREFIX, union_args) 262 add_func(scope, union_key, render_union_func(cls, union_args, tagging), g) 263 scope.union_se_args[union_key] = union_args 264 265 for f in sefields(cls, serialize_class_var): 266 if f.skip_if: 267 g[f.skip_if.name] = f.skip_if 268 if f.serializer: 269 g[f.serializer.name] = f.serializer 270 271 add_func( 272 scope, 273 TO_ITER, 274 render_to_tuple(cls, serializer, type_check, serialize_class_var, class_serializer), 275 g, 276 ) 277 add_func( 278 scope, 279 TO_DICT, 280 render_to_dict( 281 cls, rename_all, serializer, type_check, serialize_class_var, class_serializer 282 ), 283 g, 284 ) 285 286 logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}") 287 288 return cls 289 290 if _cls is None: 291 return wrap # type: ignore 292 293 if _cls in GENERATION_STACK: 294 return _cls 295 296 GENERATION_STACK.append(_cls) 297 try: 298 return wrap(_cls) 299 finally: 300 GENERATION_STACK.pop() 301 302 303def is_serializable(instance_or_class: Any) -> bool: 304 """ 305 Test if an instance or class is serializable. 306 307 >>> @serialize 308 ... class Foo: 309 ... pass 310 311 Testing `Foo` class object returns `True`. 312 >>> is_serializable(Foo) 313 True 314 315 Testing `Foo` object also returns `True`. 316 >>> is_serializable(Foo()) 317 True 318 """ 319 return hasattr(instance_or_class, SERDE_SCOPE) 320 321 322def is_dataclass_without_se(cls: type[Any]) -> bool: 323 if not dataclasses.is_dataclass(cls): 324 return False 325 if not hasattr(cls, SERDE_SCOPE): 326 return True 327 scope: Optional[Scope] = getattr(cls, SERDE_SCOPE) 328 if not scope: 329 return True 330 return TO_DICT not in scope.funcs 331 332 333def to_obj( 334 o: Any, 335 named: bool, 336 reuse_instances: Optional[bool] = None, 337 convert_sets: Optional[bool] = None, 338 skip_none: bool = False, 339 c: Optional[Any] = None, 340) -> Any: 341 def serializable_to_obj(object: Any) -> Any: 342 serde_scope: Scope = getattr(object, SERDE_SCOPE) 343 func_name = TO_DICT if named else TO_ITER 344 return serde_scope.funcs[func_name]( 345 object, 346 reuse_instances=reuse_instances, 347 convert_sets=convert_sets, 348 skip_none=skip_none, 349 ) 350 351 try: 352 thisfunc = functools.partial( 353 to_obj, 354 named=named, 355 reuse_instances=reuse_instances, 356 convert_sets=convert_sets, 357 skip_none=skip_none, 358 ) 359 360 # If a class in the argument is a non-dataclass class e.g. Union[Foo, Bar], 361 # pyserde generates a wrapper (de)serializable dataclass on the fly, 362 # and use it to serialize the object. 363 if c and is_union(c) and not is_opt(c): 364 return CACHE.serialize_union(c, o) 365 366 if o is None: 367 return None 368 if is_dataclass_without_se(o): 369 # Do not automatically implement beartype if dataclass without serde decorator 370 # is passed, because it is surprising for users 371 # See https://github.com/yukinarit/pyserde/issues/480 372 serialize(type(o), type_check=disabled) 373 return serializable_to_obj(o) 374 elif is_serializable(o): 375 return serializable_to_obj(o) 376 elif is_bearable(o, list): 377 return [thisfunc(e) for e in o] 378 elif is_bearable(o, tuple): 379 return tuple(thisfunc(e) for e in o) 380 elif is_bearable(o, set): 381 return [thisfunc(e) for e in o] 382 elif is_bearable(o, dict): 383 return {k: thisfunc(v) for k, v in o.items()} 384 elif is_str_serializable_instance(o) or is_datetime_instance(o): 385 se_cls = o.__class__ if not c or c is Any else c 386 return CACHE.serialize( 387 se_cls, 388 o, 389 reuse_instances=reuse_instances, 390 convert_sets=convert_sets, 391 skip_none=skip_none, 392 ) 393 394 return o 395 396 except Exception as e: 397 raise SerdeError(e) from None 398 399 400def astuple(v: Any) -> tuple[Any, ...]: 401 """ 402 Serialize object into tuple. 403 """ 404 return to_tuple(v, reuse_instances=False, convert_sets=False) 405 406 407def to_tuple( 408 o: Any, 409 c: Optional[type[Any]] = None, 410 reuse_instances: Optional[bool] = None, 411 convert_sets: Optional[bool] = None, 412 skip_none: bool = False, 413) -> tuple[Any, ...]: 414 """ 415 Serialize object into tuple. 416 417 >>> @serialize 418 ... class Foo: 419 ... i: int 420 ... s: str = 'foo' 421 ... f: float = 100.0 422 ... b: bool = True 423 >>> 424 >>> to_tuple(Foo(i=10)) 425 (10, 'foo', 100.0, True) 426 427 You can pass any type supported by pyserde. For example, 428 429 >>> lst = [Foo(i=10), Foo(i=20)] 430 >>> to_tuple(lst) 431 [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] 432 """ 433 return to_obj( # type: ignore 434 o, 435 named=False, 436 c=c, 437 reuse_instances=reuse_instances, 438 convert_sets=convert_sets, 439 skip_none=skip_none, 440 ) 441 442 443def asdict(v: Any) -> dict[Any, Any]: 444 """ 445 Serialize object into dictionary. 446 """ 447 return to_dict(v, reuse_instances=False, convert_sets=False) 448 449 450def to_dict( 451 o: Any, 452 c: Optional[type[Any]] = None, 453 reuse_instances: Optional[bool] = None, 454 convert_sets: Optional[bool] = None, 455 skip_none: bool = False, 456) -> dict[Any, Any]: 457 """ 458 Serialize object into python dictionary. This function ensures that the dataclass's fields are 459 accurately represented as key-value pairs in the resulting dictionary. 460 461 * `o`: Any pyserde object that you want to convert to `dict` 462 * `c`: Optional class argument 463 * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer 464 instead of converting them to serializable representation e.g. string. This behaviour allows 465 to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially 466 improve performance. 467 * `convert_sets`: This option controls how sets are handled during serialization and 468 deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during 469 serialization and back to sets during deserialization. This is useful for data formats that 470 do not natively support sets. 471 * `skip_none`: When set to True, any field in the class with a None value is excluded from the 472 serialized output. Defaults to False. 473 474 >>> from serde import serde 475 >>> @serde 476 ... class Foo: 477 ... i: int 478 ... s: str = 'foo' 479 ... f: float = 100.0 480 ... b: bool = True 481 >>> 482 >>> to_dict(Foo(i=10)) 483 {'i': 10, 's': 'foo', 'f': 100.0, 'b': True} 484 485 You can serialize not only pyserde objects but also objects of any supported types. For example, 486 the following example serializes list of pyserde objects into dict. 487 488 >>> lst = [Foo(i=10), Foo(i=20)] 489 >>> to_dict(lst) 490 [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] 491 """ 492 return to_obj( # type: ignore 493 o, 494 named=True, 495 c=c, 496 reuse_instances=reuse_instances, 497 convert_sets=convert_sets, 498 skip_none=skip_none, 499 ) 500 501 502@dataclass 503class SeField(Field[T]): 504 """ 505 Field class for serialization. 506 """ 507 508 @property 509 def varname(self) -> str: 510 """ 511 Get variable name in the generated code e.g. obj.a.b 512 """ 513 var = getattr(self.parent, "varname", None) if self.parent else None 514 if var: 515 return f"{var}.{self.name}" 516 else: 517 if self.name is None: 518 raise SerdeError("Field name is None.") 519 return self.name 520 521 def __getitem__(self, n: int) -> SeField[Any]: 522 typ = type_args(self.type)[n] 523 opts: dict[str, Any] = { 524 "kw_only": self.kw_only, 525 "case": self.case, 526 "alias": self.alias, 527 "rename": self.rename, 528 "skip": self.skip, 529 "skip_if": self.skip_if, 530 "skip_if_false": self.skip_if_false, 531 "skip_if_default": self.skip_if_default, 532 "serializer": self.serializer, 533 "deserializer": self.deserializer, 534 "flatten": self.flatten, 535 } 536 return SeField(typ, name=None, **opts) 537 538 539def sefields(cls: type[Any], serialize_class_var: bool = False) -> Iterator[SeField[Any]]: 540 """ 541 Iterate fields for serialization. 542 """ 543 for f in fields(SeField, cls, serialize_class_var=serialize_class_var): 544 f.parent = SeField(None, "obj") # type: ignore 545 yield f 546 547 548jinja2_env = jinja2.Environment( 549 loader=jinja2.DictLoader( 550 { 551 "dict": """ 552def {{func}}(obj, reuse_instances = None, convert_sets = None, skip_none = False): 553 if reuse_instances is None: 554 reuse_instances = {{serde_scope.reuse_instances_default}} 555 if convert_sets is None: 556 convert_sets = {{serde_scope.convert_sets_default}} 557 if not is_dataclass(obj): 558 return copy.deepcopy(obj) 559 560 res = {} 561 {% for f in fields -%} 562 subres = {{rvalue(f)}} 563 {% if not f.skip -%} 564 {% if f.skip_if -%} 565 if not {{f.skip_if.name}}(subres): 566 {{lvalue(f)}} = subres 567 {% else -%} 568 if skip_none: 569 if subres is not None: 570 {{lvalue(f)}} = subres 571 else: 572 {{lvalue(f)}} = subres 573 {% endif -%} 574 {% endif %} 575 576 {% endfor -%} 577 return res 578""", 579 "iter": """ 580def {{func}}(obj, reuse_instances=None, convert_sets=None, skip_none=False): 581 if reuse_instances is None: 582 reuse_instances = {{serde_scope.reuse_instances_default}} 583 if convert_sets is None: 584 convert_sets = {{serde_scope.convert_sets_default}} 585 if not is_dataclass(obj): 586 return copy.deepcopy(obj) 587 588 return ( 589 {% for f in fields -%} 590 {% if not f.skip|default(False) %} 591 {{rvalue(f)}}, 592 {% endif -%} 593 {% endfor -%} 594 ) 595""", 596 "union": """ 597def {{func}}(obj, reuse_instances, convert_sets): 598 union_args = serde_scope.union_se_args['{{func}}'] 599 600 {% for t in union_args %} 601 if is_instance(obj, union_args[{{loop.index0}}]): 602 {% if tagging.is_external() and is_taggable(t) %} 603 return {"{{typename(t)}}": {{rvalue(arg(t))}}} 604 605 {% elif tagging.is_internal() and is_taggable(t) %} 606 res = {{rvalue(arg(t))}} 607 res["{{tagging.tag}}"] = "{{typename(t)}}" 608 return res 609 610 {% elif tagging.is_adjacent() and is_taggable(t) %} 611 res = {"{{tagging.content}}": {{rvalue(arg(t))}}} 612 res["{{tagging.tag}}"] = "{{typename(t)}}" 613 return res 614 615 {% else %} 616 return {{rvalue(arg(t))}} 617 {% endif %} 618 {% endfor %} 619 raise SerdeError("Can not serialize " + \ 620 repr(obj) + \ 621 " of type " + \ 622 typename(type(obj)) + \ 623 " for {{union_name}}") 624""", 625 } 626 ) 627) 628 629 630def render_to_tuple( 631 cls: type[Any], 632 legacy_class_serializer: Optional[SerializeFunc] = None, 633 type_check: TypeCheck = strict, 634 serialize_class_var: bool = False, 635 class_serializer: Optional[ClassSerializer] = None, 636) -> str: 637 renderer = Renderer( 638 TO_ITER, 639 legacy_class_serializer, 640 suppress_coerce=(not type_check.is_coerce()), 641 serialize_class_var=serialize_class_var, 642 class_serializer=class_serializer, 643 class_name=typename(cls), 644 ) 645 return jinja2_env.get_template("iter").render( 646 func=TO_ITER, 647 serde_scope=getattr(cls, SERDE_SCOPE), 648 fields=sefields(cls, serialize_class_var), 649 type_check=type_check, 650 rvalue=renderer.render, 651 ) 652 653 654def render_to_dict( 655 cls: type[Any], 656 case: Optional[str] = None, 657 legacy_class_serializer: Optional[SerializeFunc] = None, 658 type_check: TypeCheck = strict, 659 serialize_class_var: bool = False, 660 class_serializer: Optional[ClassSerializer] = None, 661) -> str: 662 renderer = Renderer( 663 TO_DICT, 664 legacy_class_serializer, 665 suppress_coerce=(not type_check.is_coerce()), 666 class_serializer=class_serializer, 667 class_name=typename(cls), 668 ) 669 lrenderer = LRenderer(case, serialize_class_var) 670 return jinja2_env.get_template("dict").render( 671 func=TO_DICT, 672 serde_scope=getattr(cls, SERDE_SCOPE), 673 fields=sefields(cls, serialize_class_var), 674 type_check=type_check, 675 lvalue=lrenderer.render, 676 rvalue=renderer.render, 677 ) 678 679 680def render_union_func( 681 cls: type[Any], union_args: list[type[Any]], tagging: Tagging = DefaultTagging 682) -> str: 683 """ 684 Render function that serializes a field with union type. 685 """ 686 union_name = f"Union[{', '.join([typename(a) for a in union_args])}]" 687 renderer = Renderer(TO_DICT, suppress_coerce=True, class_name=typename(cls)) 688 return jinja2_env.get_template("union").render( 689 func=union_func_name(UNION_SE_PREFIX, union_args), 690 serde_scope=getattr(cls, SERDE_SCOPE), 691 union_args=union_args, 692 union_name=union_name, 693 tagging=tagging, 694 is_taggable=Tagging.is_taggable, 695 arg=lambda x: SeField(x, "obj"), 696 rvalue=renderer.render, 697 typename=typename, 698 ) 699 700 701@dataclass 702class LRenderer: 703 """ 704 Render lvalue for various types. 705 """ 706 707 case: Optional[str] 708 serialize_class_var: bool = False 709 710 def render(self, arg: SeField[Any]) -> str: 711 """ 712 Render lvalue 713 """ 714 if is_dataclass(arg.type) and arg.flatten: 715 return self.flatten(arg) 716 elif is_opt(arg.type) and arg.flatten: 717 inner = arg[0] 718 if is_dataclass(inner.type): 719 return self.flatten(inner) 720 721 return f'res["{arg.conv_name(self.case)}"]' 722 723 def flatten(self, arg: SeField[Any]) -> str: 724 """ 725 Render field with flatten attribute. 726 """ 727 flattened = [] 728 for f in sefields(arg.type, self.serialize_class_var): 729 flattened.append(self.render(f)) 730 return ", ".join(flattened) 731 732 733@dataclass 734class Renderer: 735 """ 736 Render rvalue for code generation. 737 """ 738 739 func: str 740 legacy_class_serializer: Optional[SerializeFunc] = None 741 suppress_coerce: bool = False 742 """ Suppress type coercing because generated union serializer has its own type checking """ 743 serialize_class_var: bool = False 744 class_serializer: Optional[ClassSerializer] = None 745 class_name: Optional[str] = None 746 747 def render(self, arg: SeField[Any]) -> str: 748 """ 749 Render rvalue 750 """ 751 implemented_methods: dict[type[Any], int] = {} 752 class_serializers: Iterable[ClassSerializer] = itertools.chain( 753 GLOBAL_CLASS_SERIALIZER, [self.class_serializer] if self.class_serializer else [] 754 ) 755 for n, class_serializer in enumerate(class_serializers): 756 for method in class_serializer.__class__.serialize.methods: # type: ignore 757 implemented_methods[method.signature.types[1]] = n 758 759 custom_serializer_available = arg.type in implemented_methods 760 if custom_serializer_available and not arg.serializer: 761 res = f"class_serializers[{implemented_methods[arg.type]}].serialize({arg.varname})" 762 elif arg.serializer and arg.serializer.inner is not default_serializer: 763 res = self.custom_field_serializer(arg) 764 elif is_dataclass(arg.type): 765 res = self.dataclass(arg) 766 elif is_opt(arg.type): 767 res = self.opt(arg) 768 elif is_list(arg.type): 769 res = self.list(arg) 770 elif is_set(arg.type): 771 res = self.set(arg) 772 elif is_dict(arg.type): 773 res = self.dict(arg) 774 elif is_tuple(arg.type): 775 res = self.tuple(arg) 776 elif is_enum(arg.type): 777 res = self.enum(arg) 778 elif is_numpy_datetime(arg.type): 779 res = serialize_numpy_datetime(arg) 780 elif is_numpy_scalar(arg.type): 781 res = serialize_numpy_scalar(arg) 782 elif is_numpy_array(arg.type): 783 res = serialize_numpy_array(arg) 784 elif is_numpy_jaxtyping(arg.type): 785 res = serialize_numpy_array(arg) 786 elif is_primitive(arg.type): 787 res = self.primitive(arg) 788 elif is_union(arg.type): 789 res = self.union_func(arg) 790 elif is_str_serializable(arg.type): 791 res = f"{arg.varname} if reuse_instances else {self.string(arg)}" 792 elif is_datetime(arg.type): 793 res = f"{arg.varname} if reuse_instances else {arg.varname}.isoformat()" 794 elif is_none(arg.type): 795 res = "None" 796 elif is_any(arg.type) or is_bearable(arg.type, TypeVar): 797 res = f"to_obj({arg.varname}, True, False, False, c=typing.Any)" 798 elif is_generic(arg.type): 799 origin = get_origin(arg.type) 800 assert origin 801 arg.type = origin 802 res = self.render(arg) 803 elif is_literal(arg.type): 804 res = self.literal(arg) 805 elif is_class_var(arg.type): 806 arg.type = type_args(arg.type)[0] 807 res = self.render(arg) 808 elif is_pep695_type_alias(arg.type): 809 res = self.render( 810 SeField( 811 name=arg.name, type=arg.type.__value__, parent=SeField(None.__class__, "obj") 812 ) 813 ) 814 else: 815 res = f"raise_unsupported_type({arg.varname})" 816 817 # Custom field serializer overrides custom class serializer. 818 if self.legacy_class_serializer and not arg.serializer and not custom_serializer_available: 819 return ( 820 "serde_legacy_custom_class_serializer(" 821 f"{typename(arg.type)}, " 822 f"{arg.varname}, " 823 f"default=lambda: {res})" 824 ) 825 else: 826 return res 827 828 def custom_field_serializer(self, arg: SeField[Any]) -> str: 829 """ 830 Render rvalue for the field with custom serializer. 831 """ 832 assert arg.serializer 833 return f"{arg.serializer.name}({arg.varname})" 834 835 def dataclass(self, arg: SeField[Any]) -> str: 836 """ 837 Render rvalue for dataclass. 838 """ 839 if arg.flatten: 840 flattened = [] 841 for f in sefields(arg.type, self.serialize_class_var): 842 f.parent = arg 843 flattened.append(self.render(f)) 844 return ", ".join(flattened) 845 else: 846 return ( 847 f"{arg.varname}.{SERDE_SCOPE}.funcs['{self.func}']({arg.varname}," 848 " reuse_instances=reuse_instances, convert_sets=convert_sets)" 849 ) 850 851 def opt(self, arg: SeField[Any]) -> str: 852 """ 853 Render rvalue for optional. 854 """ 855 if is_bare_opt(arg.type): 856 return f"{arg.varname} if {arg.varname} is not None else None" 857 858 inner = arg[0] 859 inner.name = arg.varname 860 if arg.flatten: 861 return self.render(inner) 862 else: 863 return f"({self.render(inner)}) if {arg.varname} is not None else None" 864 865 def list(self, arg: SeField[Any]) -> str: 866 """ 867 Render rvalue for list. 868 """ 869 if is_bare_list(arg.type): 870 return arg.varname 871 else: 872 earg = arg[0] 873 earg.name = "v" 874 return f"[{self.render(earg)} for v in {arg.varname}]" 875 876 def set(self, arg: SeField[Any]) -> str: 877 """ 878 Render rvalue for set. 879 """ 880 if is_bare_set(arg.type): 881 return f"list({arg.varname}) if convert_sets else {arg.varname}" 882 else: 883 earg = arg[0] 884 earg.name = "v" 885 return ( 886 f"[{self.render(earg)} for v in {arg.varname}] " 887 f"if convert_sets else set({self.render(earg)} for v in {arg.varname})" 888 ) 889 890 def tuple(self, arg: SeField[Any]) -> str: 891 """ 892 Render rvalue for tuple. 893 """ 894 if is_bare_tuple(arg.type): 895 return arg.varname 896 elif is_variable_tuple(arg.type): 897 earg = arg[0] 898 earg.name = "v" 899 return f"tuple({self.render(earg)} for v in {arg.varname})" 900 else: 901 rvalues = [] 902 for i, _ in enumerate(type_args(arg.type)): 903 r = arg[i] 904 r.name = f"{arg.varname}[{i}]" 905 rvalues.append(self.render(r)) 906 return f"({', '.join(rvalues)},)" # trailing , is required for single element tuples 907 908 def dict(self, arg: SeField[Any]) -> str: 909 """ 910 Render rvalue for dict. 911 """ 912 if is_bare_dict(arg.type): 913 return arg.varname 914 else: 915 karg = arg[0] 916 karg.name = "k" 917 varg = arg[1] 918 varg.name = "v" 919 return f"{{{self.render(karg)}: {self.render(varg)} for k, v in {arg.varname}.items()}}" 920 921 def enum(self, arg: SeField[Any]) -> str: 922 return f"enum_value({typename(arg.type)}, {arg.varname})" 923 924 def primitive(self, arg: SeField[Any]) -> str: 925 """ 926 Render rvalue for primitives. 927 """ 928 typ = typename(arg.type) 929 var = arg.varname 930 if self.suppress_coerce: 931 return var 932 else: 933 assert arg.name 934 escaped_arg_name = arg.name.replace('"', '\\"') 935 return f'coerce_object("{self.class_name}", "{escaped_arg_name}", {typ}, {var})' 936 937 def string(self, arg: SeField[Any]) -> str: 938 return f"str({arg.varname})" 939 940 def union_func(self, arg: SeField[Any]) -> str: 941 func_name = union_func_name(UNION_SE_PREFIX, list(type_args(arg.type))) 942 return f"serde_scope.funcs['{func_name}']({arg.varname}, reuse_instances, convert_sets)" 943 944 def literal(self, arg: SeField[Any]) -> str: 945 return f"{arg.varname}" 946 947 948def enum_value(cls: Any, e: Any) -> Any: 949 """ 950 Helper function to get value from enum or enum compatible value. 951 """ 952 if is_enum(e): 953 v = e.value 954 # Recursively get value of Nested enum. 955 if is_enum(v): 956 return enum_value(v.__class__, v) 957 else: 958 return v 959 else: 960 return cls(e).value
163@dataclass_transform() 164def serialize( 165 _cls: Optional[type[T]] = None, 166 rename_all: Optional[str] = None, 167 reuse_instances_default: bool = False, 168 convert_sets_default: bool = False, 169 serializer: Optional[SerializeFunc] = None, 170 tagging: Tagging = DefaultTagging, 171 type_check: TypeCheck = strict, 172 serialize_class_var: bool = False, 173 class_serializer: Optional[ClassSerializer] = None, 174 **kwargs: Any, 175) -> type[T]: 176 """ 177 A dataclass with this decorator is serializable into any of the data formats 178 supported by pyserde. 179 180 >>> from datetime import datetime 181 >>> from serde import serialize 182 >>> from serde.json import to_json 183 >>> 184 >>> @serialize 185 ... class Foo: 186 ... i: int 187 ... s: str 188 ... f: float 189 ... b: bool 190 >>> 191 >>> to_json(Foo(i=10, s='foo', f=100.0, b=True)) 192 '{"i":10,"s":"foo","f":100.0,"b":true}' 193 """ 194 195 def wrap(cls: type[T]) -> type[T]: 196 tagging.check() 197 198 # If no `dataclass` found in the class, dataclassify it automatically. 199 if not is_dataclass(cls): 200 dataclass(cls) 201 202 if type_check.is_strict(): 203 serde_beartype = beartype(conf=BeartypeConf(violation_type=SerdeError)) 204 serde_beartype(cls) 205 206 g: dict[str, Any] = {} 207 208 # Create a scope storage used by serde. 209 # Each class should get own scope. Child classes can not share scope with parent class. 210 # That's why we need the "scope.cls is not cls" check. 211 scope: Optional[Scope] = getattr(cls, SERDE_SCOPE, None) 212 if scope is None or scope.cls is not cls: 213 scope = Scope( 214 cls, 215 reuse_instances_default=reuse_instances_default, 216 convert_sets_default=convert_sets_default, 217 ) 218 setattr(cls, SERDE_SCOPE, scope) 219 220 class_serializers: list[ClassSerializer] = list( 221 itertools.chain(GLOBAL_CLASS_SERIALIZER, [class_serializer] if class_serializer else []) 222 ) 223 224 # Set some globals for all generated functions 225 g["cls"] = cls 226 g["copy"] = copy 227 g["serde_scope"] = scope 228 g["SerdeError"] = SerdeError 229 g["raise_unsupported_type"] = raise_unsupported_type 230 g["enum_value"] = enum_value 231 g["is_dataclass"] = is_dataclass 232 g["typename"] = typename # used in union functions 233 g["is_instance"] = is_instance # used in union functions 234 g["to_obj"] = to_obj 235 g["typing"] = typing 236 g["Literal"] = Literal 237 g["TypeCheck"] = TypeCheck 238 g["disabled"] = disabled 239 g["coerce_object"] = coerce_object 240 g["class_serializers"] = class_serializers 241 if serializer: 242 g["serde_legacy_custom_class_serializer"] = functools.partial( 243 serde_legacy_custom_class_serializer, custom=serializer 244 ) 245 246 # Collect types used in the generated code. 247 for typ in iter_types(cls): 248 # When we encounter a dataclass not marked with serialize, then also generate serialize 249 # functions for it. 250 if is_dataclass_without_se(typ) and typ is not cls: 251 # We call serialize and not wrap to make sure that we will use the default serde 252 # configuration for generating the serialization function. 253 serialize(typ) 254 255 if is_primitive(typ) and not is_enum(typ): 256 continue 257 g[typename(typ)] = typ 258 259 # render all union functions 260 for union in iter_unions(cls): 261 union_args = list(type_args(union)) 262 union_key = union_func_name(UNION_SE_PREFIX, union_args) 263 add_func(scope, union_key, render_union_func(cls, union_args, tagging), g) 264 scope.union_se_args[union_key] = union_args 265 266 for f in sefields(cls, serialize_class_var): 267 if f.skip_if: 268 g[f.skip_if.name] = f.skip_if 269 if f.serializer: 270 g[f.serializer.name] = f.serializer 271 272 add_func( 273 scope, 274 TO_ITER, 275 render_to_tuple(cls, serializer, type_check, serialize_class_var, class_serializer), 276 g, 277 ) 278 add_func( 279 scope, 280 TO_DICT, 281 render_to_dict( 282 cls, rename_all, serializer, type_check, serialize_class_var, class_serializer 283 ), 284 g, 285 ) 286 287 logger.debug(f"{typename(cls)}: {SERDE_SCOPE} {scope}") 288 289 return cls 290 291 if _cls is None: 292 return wrap # type: ignore 293 294 if _cls in GENERATION_STACK: 295 return _cls 296 297 GENERATION_STACK.append(_cls) 298 try: 299 return wrap(_cls) 300 finally: 301 GENERATION_STACK.pop()
A dataclass with this decorator is serializable into any of the data formats supported by pyserde.
>>> from datetime import datetime
>>> from serde import serialize
>>> from serde.json import to_json
>>>
>>> @serialize
... class Foo:
... i: int
... s: str
... f: float
... b: bool
>>>
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
304def is_serializable(instance_or_class: Any) -> bool: 305 """ 306 Test if an instance or class is serializable. 307 308 >>> @serialize 309 ... class Foo: 310 ... pass 311 312 Testing `Foo` class object returns `True`. 313 >>> is_serializable(Foo) 314 True 315 316 Testing `Foo` object also returns `True`. 317 >>> is_serializable(Foo()) 318 True 319 """ 320 return hasattr(instance_or_class, SERDE_SCOPE)
Test if an instance or class is serializable.
>>> @serialize
... class Foo:
... pass
Testing Foo
class object returns True
.
>>> is_serializable(Foo)
True
Testing Foo
object also returns True
.
>>> is_serializable(Foo())
True
451def to_dict( 452 o: Any, 453 c: Optional[type[Any]] = None, 454 reuse_instances: Optional[bool] = None, 455 convert_sets: Optional[bool] = None, 456 skip_none: bool = False, 457) -> dict[Any, Any]: 458 """ 459 Serialize object into python dictionary. This function ensures that the dataclass's fields are 460 accurately represented as key-value pairs in the resulting dictionary. 461 462 * `o`: Any pyserde object that you want to convert to `dict` 463 * `c`: Optional class argument 464 * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer 465 instead of converting them to serializable representation e.g. string. This behaviour allows 466 to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially 467 improve performance. 468 * `convert_sets`: This option controls how sets are handled during serialization and 469 deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during 470 serialization and back to sets during deserialization. This is useful for data formats that 471 do not natively support sets. 472 * `skip_none`: When set to True, any field in the class with a None value is excluded from the 473 serialized output. Defaults to False. 474 475 >>> from serde import serde 476 >>> @serde 477 ... class Foo: 478 ... i: int 479 ... s: str = 'foo' 480 ... f: float = 100.0 481 ... b: bool = True 482 >>> 483 >>> to_dict(Foo(i=10)) 484 {'i': 10, 's': 'foo', 'f': 100.0, 'b': True} 485 486 You can serialize not only pyserde objects but also objects of any supported types. For example, 487 the following example serializes list of pyserde objects into dict. 488 489 >>> lst = [Foo(i=10), Foo(i=20)] 490 >>> to_dict(lst) 491 [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] 492 """ 493 return to_obj( # type: ignore 494 o, 495 named=True, 496 c=c, 497 reuse_instances=reuse_instances, 498 convert_sets=convert_sets, 499 skip_none=skip_none, 500 )
Serialize object into python dictionary. This function ensures that the dataclass's fields are accurately represented as key-value pairs in the resulting dictionary.
o
: Any pyserde object that you want to convert todict
c
: Optional class argumentreuse_instances
: pyserde will pass instances (e.g. Path, datetime) directly to serializer instead of converting them to serializable representation e.g. string. This behaviour allows to delegate serializtation to underlying data format packages e.g.pyyaml
and potentially improve performance.convert_sets
: This option controls how sets are handled during serialization and deserialization. Whenconvert_sets
is set to True, pyserde will convert sets to lists during serialization and back to sets during deserialization. This is useful for data formats that do not natively support sets.skip_none
: When set to True, any field in the class with a None value is excluded from the serialized output. Defaults to False.
>>> from serde import serde
>>> @serde
... class Foo:
... i: int
... s: str = 'foo'
... f: float = 100.0
... b: bool = True
>>>
>>> to_dict(Foo(i=10))
{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}
You can serialize not only pyserde objects but also objects of any supported types. For example, the following example serializes list of pyserde objects into dict.
>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_dict(lst)
[{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
408def to_tuple( 409 o: Any, 410 c: Optional[type[Any]] = None, 411 reuse_instances: Optional[bool] = None, 412 convert_sets: Optional[bool] = None, 413 skip_none: bool = False, 414) -> tuple[Any, ...]: 415 """ 416 Serialize object into tuple. 417 418 >>> @serialize 419 ... class Foo: 420 ... i: int 421 ... s: str = 'foo' 422 ... f: float = 100.0 423 ... b: bool = True 424 >>> 425 >>> to_tuple(Foo(i=10)) 426 (10, 'foo', 100.0, True) 427 428 You can pass any type supported by pyserde. For example, 429 430 >>> lst = [Foo(i=10), Foo(i=20)] 431 >>> to_tuple(lst) 432 [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] 433 """ 434 return to_obj( # type: ignore 435 o, 436 named=False, 437 c=c, 438 reuse_instances=reuse_instances, 439 convert_sets=convert_sets, 440 skip_none=skip_none, 441 )
Serialize object into tuple.
>>> @serialize
... class Foo:
... i: int
... s: str = 'foo'
... f: float = 100.0
... b: bool = True
>>>
>>> to_tuple(Foo(i=10))
(10, 'foo', 100.0, True)
You can pass any type supported by pyserde. For example,
>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_tuple(lst)
[(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]