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