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