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 return CACHE.serialize( 385 c or o.__class__, 386 o, 387 reuse_instances=reuse_instances, 388 convert_sets=convert_sets, 389 skip_none=skip_none, 390 ) 391 392 return o 393 394 except Exception as e: 395 raise SerdeError(e) from None 396 397 398def astuple(v: Any) -> tuple[Any, ...]: 399 """ 400 Serialize object into tuple. 401 """ 402 return to_tuple(v, reuse_instances=False, convert_sets=False) 403 404 405def to_tuple( 406 o: Any, 407 c: Optional[type[Any]] = None, 408 reuse_instances: Optional[bool] = None, 409 convert_sets: Optional[bool] = None, 410 skip_none: bool = False, 411) -> tuple[Any, ...]: 412 """ 413 Serialize object into tuple. 414 415 >>> @serialize 416 ... class Foo: 417 ... i: int 418 ... s: str = 'foo' 419 ... f: float = 100.0 420 ... b: bool = True 421 >>> 422 >>> to_tuple(Foo(i=10)) 423 (10, 'foo', 100.0, True) 424 425 You can pass any type supported by pyserde. For example, 426 427 >>> lst = [Foo(i=10), Foo(i=20)] 428 >>> to_tuple(lst) 429 [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] 430 """ 431 return to_obj( # type: ignore 432 o, 433 named=False, 434 c=c, 435 reuse_instances=reuse_instances, 436 convert_sets=convert_sets, 437 skip_none=skip_none, 438 ) 439 440 441def asdict(v: Any) -> dict[Any, Any]: 442 """ 443 Serialize object into dictionary. 444 """ 445 return to_dict(v, reuse_instances=False, convert_sets=False) 446 447 448def to_dict( 449 o: Any, 450 c: Optional[type[Any]] = None, 451 reuse_instances: Optional[bool] = None, 452 convert_sets: Optional[bool] = None, 453 skip_none: bool = False, 454) -> dict[Any, Any]: 455 """ 456 Serialize object into python dictionary. This function ensures that the dataclass's fields are 457 accurately represented as key-value pairs in the resulting dictionary. 458 459 * `o`: Any pyserde object that you want to convert to `dict` 460 * `c`: Optional class argument 461 * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer 462 instead of converting them to serializable representation e.g. string. This behaviour allows 463 to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially 464 improve performance. 465 * `convert_sets`: This option controls how sets are handled during serialization and 466 deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during 467 serialization and back to sets during deserialization. This is useful for data formats that 468 do not natively support sets. 469 * `skip_none`: When set to True, any field in the class with a None value is excluded from the 470 serialized output. Defaults to False. 471 472 >>> from serde import serde 473 >>> @serde 474 ... class Foo: 475 ... i: int 476 ... s: str = 'foo' 477 ... f: float = 100.0 478 ... b: bool = True 479 >>> 480 >>> to_dict(Foo(i=10)) 481 {'i': 10, 's': 'foo', 'f': 100.0, 'b': True} 482 483 You can serialize not only pyserde objects but also objects of any supported types. For example, 484 the following example serializes list of pyserde objects into dict. 485 486 >>> lst = [Foo(i=10), Foo(i=20)] 487 >>> to_dict(lst) 488 [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] 489 """ 490 return to_obj( # type: ignore 491 o, 492 named=True, 493 c=c, 494 reuse_instances=reuse_instances, 495 convert_sets=convert_sets, 496 skip_none=skip_none, 497 ) 498 499 500@dataclass 501class SeField(Field[T]): 502 """ 503 Field class for serialization. 504 """ 505 506 @property 507 def varname(self) -> str: 508 """ 509 Get variable name in the generated code e.g. obj.a.b 510 """ 511 var = getattr(self.parent, "varname", None) if self.parent else None 512 if var: 513 return f"{var}.{self.name}" 514 else: 515 if self.name is None: 516 raise SerdeError("Field name is None.") 517 return self.name 518 519 def __getitem__(self, n: int) -> SeField[Any]: 520 typ = type_args(self.type)[n] 521 opts: dict[str, Any] = { 522 "kw_only": self.kw_only, 523 "case": self.case, 524 "alias": self.alias, 525 "rename": self.rename, 526 "skip": self.skip, 527 "skip_if": self.skip_if, 528 "skip_if_false": self.skip_if_false, 529 "skip_if_default": self.skip_if_default, 530 "serializer": self.serializer, 531 "deserializer": self.deserializer, 532 "flatten": self.flatten, 533 } 534 return SeField(typ, name=None, **opts) 535 536 537def sefields(cls: type[Any], serialize_class_var: bool = False) -> Iterator[SeField[Any]]: 538 """ 539 Iterate fields for serialization. 540 """ 541 for f in fields(SeField, cls, serialize_class_var=serialize_class_var): 542 f.parent = SeField(None, "obj") # type: ignore 543 yield f 544 545 546jinja2_env = jinja2.Environment( 547 loader=jinja2.DictLoader( 548 { 549 "dict": """ 550def {{func}}(obj, reuse_instances = None, convert_sets = None, skip_none = False): 551 if reuse_instances is None: 552 reuse_instances = {{serde_scope.reuse_instances_default}} 553 if convert_sets is None: 554 convert_sets = {{serde_scope.convert_sets_default}} 555 if not is_dataclass(obj): 556 return copy.deepcopy(obj) 557 558 res = {} 559 {% for f in fields -%} 560 subres = {{rvalue(f)}} 561 {% if not f.skip -%} 562 {% if f.skip_if -%} 563 if not {{f.skip_if.name}}(subres): 564 {{lvalue(f)}} = subres 565 {% else -%} 566 if skip_none: 567 if subres is not None: 568 {{lvalue(f)}} = subres 569 else: 570 {{lvalue(f)}} = subres 571 {% endif -%} 572 {% endif %} 573 574 {% endfor -%} 575 return res 576""", 577 "iter": """ 578def {{func}}(obj, reuse_instances=None, convert_sets=None, skip_none=False): 579 if reuse_instances is None: 580 reuse_instances = {{serde_scope.reuse_instances_default}} 581 if convert_sets is None: 582 convert_sets = {{serde_scope.convert_sets_default}} 583 if not is_dataclass(obj): 584 return copy.deepcopy(obj) 585 586 return ( 587 {% for f in fields -%} 588 {% if not f.skip|default(False) %} 589 {{rvalue(f)}}, 590 {% endif -%} 591 {% endfor -%} 592 ) 593""", 594 "union": """ 595def {{func}}(obj, reuse_instances, convert_sets): 596 union_args = serde_scope.union_se_args['{{func}}'] 597 598 {% for t in union_args %} 599 if is_instance(obj, union_args[{{loop.index0}}]): 600 {% if tagging.is_external() and is_taggable(t) %} 601 return {"{{typename(t)}}": {{rvalue(arg(t))}}} 602 603 {% elif tagging.is_internal() and is_taggable(t) %} 604 res = {{rvalue(arg(t))}} 605 res["{{tagging.tag}}"] = "{{typename(t)}}" 606 return res 607 608 {% elif tagging.is_adjacent() and is_taggable(t) %} 609 res = {"{{tagging.content}}": {{rvalue(arg(t))}}} 610 res["{{tagging.tag}}"] = "{{typename(t)}}" 611 return res 612 613 {% else %} 614 return {{rvalue(arg(t))}} 615 {% endif %} 616 {% endfor %} 617 raise SerdeError("Can not serialize " + \ 618 repr(obj) + \ 619 " of type " + \ 620 typename(type(obj)) + \ 621 " for {{union_name}}") 622""", 623 } 624 ) 625) 626 627 628def render_to_tuple( 629 cls: type[Any], 630 legacy_class_serializer: Optional[SerializeFunc] = None, 631 type_check: TypeCheck = strict, 632 serialize_class_var: bool = False, 633 class_serializer: Optional[ClassSerializer] = None, 634) -> str: 635 renderer = Renderer( 636 TO_ITER, 637 legacy_class_serializer, 638 suppress_coerce=(not type_check.is_coerce()), 639 serialize_class_var=serialize_class_var, 640 class_serializer=class_serializer, 641 class_name=typename(cls), 642 ) 643 return jinja2_env.get_template("iter").render( 644 func=TO_ITER, 645 serde_scope=getattr(cls, SERDE_SCOPE), 646 fields=sefields(cls, serialize_class_var), 647 type_check=type_check, 648 rvalue=renderer.render, 649 ) 650 651 652def render_to_dict( 653 cls: type[Any], 654 case: Optional[str] = None, 655 legacy_class_serializer: Optional[SerializeFunc] = None, 656 type_check: TypeCheck = strict, 657 serialize_class_var: bool = False, 658 class_serializer: Optional[ClassSerializer] = None, 659) -> str: 660 renderer = Renderer( 661 TO_DICT, 662 legacy_class_serializer, 663 suppress_coerce=(not type_check.is_coerce()), 664 class_serializer=class_serializer, 665 class_name=typename(cls), 666 ) 667 lrenderer = LRenderer(case, serialize_class_var) 668 return jinja2_env.get_template("dict").render( 669 func=TO_DICT, 670 serde_scope=getattr(cls, SERDE_SCOPE), 671 fields=sefields(cls, serialize_class_var), 672 type_check=type_check, 673 lvalue=lrenderer.render, 674 rvalue=renderer.render, 675 ) 676 677 678def render_union_func( 679 cls: type[Any], union_args: list[type[Any]], tagging: Tagging = DefaultTagging 680) -> str: 681 """ 682 Render function that serializes a field with union type. 683 """ 684 union_name = f"Union[{', '.join([typename(a) for a in union_args])}]" 685 renderer = Renderer(TO_DICT, suppress_coerce=True, class_name=typename(cls)) 686 return jinja2_env.get_template("union").render( 687 func=union_func_name(UNION_SE_PREFIX, union_args), 688 serde_scope=getattr(cls, SERDE_SCOPE), 689 union_args=union_args, 690 union_name=union_name, 691 tagging=tagging, 692 is_taggable=Tagging.is_taggable, 693 arg=lambda x: SeField(x, "obj"), 694 rvalue=renderer.render, 695 typename=typename, 696 ) 697 698 699@dataclass 700class LRenderer: 701 """ 702 Render lvalue for various types. 703 """ 704 705 case: Optional[str] 706 serialize_class_var: bool = False 707 708 def render(self, arg: SeField[Any]) -> str: 709 """ 710 Render lvalue 711 """ 712 if is_dataclass(arg.type) and arg.flatten: 713 return self.flatten(arg) 714 elif is_opt(arg.type) and arg.flatten: 715 inner = arg[0] 716 if is_dataclass(inner.type): 717 return self.flatten(inner) 718 719 return f'res["{arg.conv_name(self.case)}"]' 720 721 def flatten(self, arg: SeField[Any]) -> str: 722 """ 723 Render field with flatten attribute. 724 """ 725 flattened = [] 726 for f in sefields(arg.type, self.serialize_class_var): 727 flattened.append(self.render(f)) 728 return ", ".join(flattened) 729 730 731@dataclass 732class Renderer: 733 """ 734 Render rvalue for code generation. 735 """ 736 737 func: str 738 legacy_class_serializer: Optional[SerializeFunc] = None 739 suppress_coerce: bool = False 740 """ Suppress type coercing because generated union serializer has its own type checking """ 741 serialize_class_var: bool = False 742 class_serializer: Optional[ClassSerializer] = None 743 class_name: Optional[str] = None 744 745 def render(self, arg: SeField[Any]) -> str: 746 """ 747 Render rvalue 748 """ 749 implemented_methods: dict[type[Any], int] = {} 750 class_serializers: Iterable[ClassSerializer] = itertools.chain( 751 GLOBAL_CLASS_SERIALIZER, [self.class_serializer] if self.class_serializer else [] 752 ) 753 for n, class_serializer in enumerate(class_serializers): 754 for method in class_serializer.__class__.serialize.methods: # type: ignore 755 implemented_methods[method.signature.types[1]] = n 756 757 custom_serializer_available = arg.type in implemented_methods 758 if custom_serializer_available and not arg.serializer: 759 res = f"class_serializers[{implemented_methods[arg.type]}].serialize({arg.varname})" 760 elif arg.serializer and arg.serializer.inner is not default_serializer: 761 res = self.custom_field_serializer(arg) 762 elif is_dataclass(arg.type): 763 res = self.dataclass(arg) 764 elif is_opt(arg.type): 765 res = self.opt(arg) 766 elif is_list(arg.type): 767 res = self.list(arg) 768 elif is_set(arg.type): 769 res = self.set(arg) 770 elif is_dict(arg.type): 771 res = self.dict(arg) 772 elif is_tuple(arg.type): 773 res = self.tuple(arg) 774 elif is_enum(arg.type): 775 res = self.enum(arg) 776 elif is_numpy_datetime(arg.type): 777 res = serialize_numpy_datetime(arg) 778 elif is_numpy_scalar(arg.type): 779 res = serialize_numpy_scalar(arg) 780 elif is_numpy_array(arg.type): 781 res = serialize_numpy_array(arg) 782 elif is_numpy_jaxtyping(arg.type): 783 res = serialize_numpy_array(arg) 784 elif is_primitive(arg.type): 785 res = self.primitive(arg) 786 elif is_union(arg.type): 787 res = self.union_func(arg) 788 elif is_str_serializable(arg.type): 789 res = f"{arg.varname} if reuse_instances else {self.string(arg)}" 790 elif is_datetime(arg.type): 791 res = f"{arg.varname} if reuse_instances else {arg.varname}.isoformat()" 792 elif is_none(arg.type): 793 res = "None" 794 elif is_any(arg.type) or is_bearable(arg.type, TypeVar): 795 res = f"to_obj({arg.varname}, True, False, False, c=typing.Any)" 796 elif is_generic(arg.type): 797 origin = get_origin(arg.type) 798 assert origin 799 arg.type = origin 800 res = self.render(arg) 801 elif is_literal(arg.type): 802 res = self.literal(arg) 803 elif is_class_var(arg.type): 804 arg.type = type_args(arg.type)[0] 805 res = self.render(arg) 806 else: 807 res = f"raise_unsupported_type({arg.varname})" 808 809 # Custom field serializer overrides custom class serializer. 810 if self.legacy_class_serializer and not arg.serializer and not custom_serializer_available: 811 return ( 812 "serde_legacy_custom_class_serializer(" 813 f"{typename(arg.type)}, " 814 f"{arg.varname}, " 815 f"default=lambda: {res})" 816 ) 817 else: 818 return res 819 820 def custom_field_serializer(self, arg: SeField[Any]) -> str: 821 """ 822 Render rvalue for the field with custom serializer. 823 """ 824 assert arg.serializer 825 return f"{arg.serializer.name}({arg.varname})" 826 827 def dataclass(self, arg: SeField[Any]) -> str: 828 """ 829 Render rvalue for dataclass. 830 """ 831 if arg.flatten: 832 flattened = [] 833 for f in sefields(arg.type, self.serialize_class_var): 834 f.parent = arg 835 flattened.append(self.render(f)) 836 return ", ".join(flattened) 837 else: 838 return ( 839 f"{arg.varname}.{SERDE_SCOPE}.funcs['{self.func}']({arg.varname}," 840 " reuse_instances=reuse_instances, convert_sets=convert_sets)" 841 ) 842 843 def opt(self, arg: SeField[Any]) -> str: 844 """ 845 Render rvalue for optional. 846 """ 847 if is_bare_opt(arg.type): 848 return f"{arg.varname} if {arg.varname} is not None else None" 849 850 inner = arg[0] 851 inner.name = arg.varname 852 if arg.flatten: 853 return self.render(inner) 854 else: 855 return f"({self.render(inner)}) if {arg.varname} is not None else None" 856 857 def list(self, arg: SeField[Any]) -> str: 858 """ 859 Render rvalue for list. 860 """ 861 if is_bare_list(arg.type): 862 return arg.varname 863 else: 864 earg = arg[0] 865 earg.name = "v" 866 return f"[{self.render(earg)} for v in {arg.varname}]" 867 868 def set(self, arg: SeField[Any]) -> str: 869 """ 870 Render rvalue for set. 871 """ 872 if is_bare_set(arg.type): 873 return f"list({arg.varname}) if convert_sets else {arg.varname}" 874 else: 875 earg = arg[0] 876 earg.name = "v" 877 return ( 878 f"[{self.render(earg)} for v in {arg.varname}] " 879 f"if convert_sets else set({self.render(earg)} for v in {arg.varname})" 880 ) 881 882 def tuple(self, arg: SeField[Any]) -> str: 883 """ 884 Render rvalue for tuple. 885 """ 886 if is_bare_tuple(arg.type): 887 return arg.varname 888 elif is_variable_tuple(arg.type): 889 earg = arg[0] 890 earg.name = "v" 891 return f"tuple({self.render(earg)} for v in {arg.varname})" 892 else: 893 rvalues = [] 894 for i, _ in enumerate(type_args(arg.type)): 895 r = arg[i] 896 r.name = f"{arg.varname}[{i}]" 897 rvalues.append(self.render(r)) 898 return f"({', '.join(rvalues)},)" # trailing , is required for single element tuples 899 900 def dict(self, arg: SeField[Any]) -> str: 901 """ 902 Render rvalue for dict. 903 """ 904 if is_bare_dict(arg.type): 905 return arg.varname 906 else: 907 karg = arg[0] 908 karg.name = "k" 909 varg = arg[1] 910 varg.name = "v" 911 return f"{{{self.render(karg)}: {self.render(varg)} for k, v in {arg.varname}.items()}}" 912 913 def enum(self, arg: SeField[Any]) -> str: 914 return f"enum_value({typename(arg.type)}, {arg.varname})" 915 916 def primitive(self, arg: SeField[Any]) -> str: 917 """ 918 Render rvalue for primitives. 919 """ 920 typ = typename(arg.type) 921 var = arg.varname 922 if self.suppress_coerce: 923 return var 924 else: 925 return f'coerce_object("{self.class_name}", "{arg.name}", {typ}, {var})' 926 927 def string(self, arg: SeField[Any]) -> str: 928 return f"str({arg.varname})" 929 930 def union_func(self, arg: SeField[Any]) -> str: 931 func_name = union_func_name(UNION_SE_PREFIX, list(type_args(arg.type))) 932 return f"serde_scope.funcs['{func_name}']({arg.varname}, reuse_instances, convert_sets)" 933 934 def literal(self, arg: SeField[Any]) -> str: 935 return f"{arg.varname}" 936 937 938def enum_value(cls: Any, e: Any) -> Any: 939 """ 940 Helper function to get value from enum or enum compatible value. 941 """ 942 if is_enum(e): 943 v = e.value 944 # Recursively get value of Nested enum. 945 if is_enum(v): 946 return enum_value(v.__class__, v) 947 else: 948 return v 949 else: 950 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
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 )
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}]
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 )
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)]