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