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