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 conv, 71 fields, 72 is_instance, 73 logger, 74 raise_unsupported_type, 75 union_func_name, 76 GLOBAL_CLASS_SERIALIZER, 77) 78from .numpy import ( 79 is_numpy_array, 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): 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 c: Optional[Any] = None, 338) -> Any: 339 def serializable_to_obj(object: Any) -> Any: 340 serde_scope: Scope = getattr(object, SERDE_SCOPE) 341 func_name = TO_DICT if named else TO_ITER 342 return serde_scope.funcs[func_name]( 343 object, reuse_instances=reuse_instances, convert_sets=convert_sets 344 ) 345 346 try: 347 thisfunc = functools.partial( 348 to_obj, 349 named=named, 350 reuse_instances=reuse_instances, 351 convert_sets=convert_sets, 352 ) 353 354 # If a class in the argument is a non-dataclass class e.g. Union[Foo, Bar], 355 # pyserde generates a wrapper (de)serializable dataclass on the fly, 356 # and use it to serialize the object. 357 if c and is_union(c) and not is_opt(c): 358 return CACHE.serialize_union(c, o) 359 360 if o is None: 361 return None 362 if is_dataclass_without_se(o): 363 # Do not automatically implement beartype if dataclass without serde decorator 364 # is passed, because it is surprising for users 365 # See https://github.com/yukinarit/pyserde/issues/480 366 serialize(type(o), type_check=disabled) 367 return serializable_to_obj(o) 368 elif is_serializable(o): 369 return serializable_to_obj(o) 370 elif is_bearable(o, list): 371 return [thisfunc(e) for e in o] 372 elif is_bearable(o, tuple): 373 return tuple(thisfunc(e) for e in o) 374 elif is_bearable(o, set): 375 return [thisfunc(e) for e in o] 376 elif is_bearable(o, dict): 377 return {k: thisfunc(v) for k, v in o.items()} 378 elif is_str_serializable_instance(o) or is_datetime_instance(o): 379 return CACHE.serialize( 380 c or o.__class__, o, reuse_instances=reuse_instances, convert_sets=convert_sets 381 ) 382 383 return o 384 385 except Exception as e: 386 raise SerdeError(e) from None 387 388 389def astuple(v: Any) -> tuple[Any, ...]: 390 """ 391 Serialize object into tuple. 392 """ 393 return to_tuple(v, reuse_instances=False, convert_sets=False) 394 395 396def to_tuple( 397 o: Any, 398 c: Optional[type[Any]] = None, 399 reuse_instances: Optional[bool] = None, 400 convert_sets: Optional[bool] = None, 401) -> tuple[Any, ...]: 402 """ 403 Serialize object into tuple. 404 405 >>> @serialize 406 ... class Foo: 407 ... i: int 408 ... s: str = 'foo' 409 ... f: float = 100.0 410 ... b: bool = True 411 >>> 412 >>> to_tuple(Foo(i=10)) 413 (10, 'foo', 100.0, True) 414 415 You can pass any type supported by pyserde. For example, 416 417 >>> lst = [Foo(i=10), Foo(i=20)] 418 >>> to_tuple(lst) 419 [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] 420 """ 421 return to_obj( # type: ignore 422 o, named=False, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets 423 ) 424 425 426def asdict(v: Any) -> dict[Any, Any]: 427 """ 428 Serialize object into dictionary. 429 """ 430 return to_dict(v, reuse_instances=False, convert_sets=False) 431 432 433def to_dict( 434 o: Any, 435 c: Optional[type[Any]] = None, 436 reuse_instances: Optional[bool] = None, 437 convert_sets: Optional[bool] = None, 438) -> dict[Any, Any]: 439 """ 440 Serialize object into dictionary. 441 442 >>> @serialize 443 ... class Foo: 444 ... i: int 445 ... s: str = 'foo' 446 ... f: float = 100.0 447 ... b: bool = True 448 >>> 449 >>> to_dict(Foo(i=10)) 450 {'i': 10, 's': 'foo', 'f': 100.0, 'b': True} 451 452 You can pass any type supported by pyserde. For example, 453 454 >>> lst = [Foo(i=10), Foo(i=20)] 455 >>> to_dict(lst) 456 [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] 457 """ 458 return to_obj( # type: ignore 459 o, named=True, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets 460 ) 461 462 463@dataclass 464class SeField(Field[T]): 465 """ 466 Field class for serialization. 467 """ 468 469 @property 470 def varname(self) -> str: 471 """ 472 Get variable name in the generated code e.g. obj.a.b 473 """ 474 var = getattr(self.parent, "varname", None) if self.parent else None 475 if var: 476 return f"{var}.{self.name}" 477 else: 478 if self.name is None: 479 raise SerdeError("Field name is None.") 480 return self.name 481 482 def __getitem__(self, n: int) -> SeField[Any]: 483 typ = type_args(self.type)[n] 484 return SeField(typ, name=None) 485 486 487def sefields(cls: type[Any], serialize_class_var: bool = False) -> Iterator[SeField[Any]]: 488 """ 489 Iterate fields for serialization. 490 """ 491 for f in fields(SeField, cls, serialize_class_var=serialize_class_var): 492 f.parent = SeField(None, "obj") # type: ignore 493 yield f 494 495 496def render_to_tuple( 497 cls: type[Any], 498 legacy_class_serializer: Optional[SerializeFunc] = None, 499 type_check: TypeCheck = strict, 500 serialize_class_var: bool = False, 501 class_serializer: Optional[ClassSerializer] = None, 502) -> str: 503 template = """ 504def {{func}}(obj, reuse_instances=None, convert_sets=None): 505 if reuse_instances is None: 506 reuse_instances = {{serde_scope.reuse_instances_default}} 507 if convert_sets is None: 508 convert_sets = {{serde_scope.convert_sets_default}} 509 if not is_dataclass(obj): 510 return copy.deepcopy(obj) 511 512 return ( 513 {% for f in fields -%} 514 {% if not f.skip|default(False) %} 515 {{f|rvalue()}}, 516 {% endif -%} 517 {% endfor -%} 518 ) 519 """ 520 521 renderer = Renderer( 522 TO_ITER, 523 legacy_class_serializer, 524 suppress_coerce=(not type_check.is_coerce()), 525 serialize_class_var=serialize_class_var, 526 class_serializer=class_serializer, 527 ) 528 env = jinja2.Environment(loader=jinja2.DictLoader({"iter": template})) 529 env.filters.update({"rvalue": renderer.render}) 530 return env.get_template("iter").render( 531 func=TO_ITER, 532 serde_scope=getattr(cls, SERDE_SCOPE), 533 fields=sefields(cls, serialize_class_var), 534 type_check=type_check, 535 ) 536 537 538def render_to_dict( 539 cls: type[Any], 540 case: Optional[str] = None, 541 legacy_class_serializer: Optional[SerializeFunc] = None, 542 type_check: TypeCheck = strict, 543 serialize_class_var: bool = False, 544 class_serializer: Optional[ClassSerializer] = None, 545) -> str: 546 template = """ 547def {{func}}(obj, reuse_instances = None, convert_sets = None): 548 if reuse_instances is None: 549 reuse_instances = {{serde_scope.reuse_instances_default}} 550 if convert_sets is None: 551 convert_sets = {{serde_scope.convert_sets_default}} 552 if not is_dataclass(obj): 553 return copy.deepcopy(obj) 554 555 res = {} 556 {% for f in fields -%} 557 {% if not f.skip -%} 558 {% if f.skip_if -%} 559 subres = {{f|rvalue}} 560 if not {{f.skip_if.name}}(subres): 561 {{f|lvalue}} = subres 562 {% else -%} 563 {{f|lvalue}} = {{f|rvalue}} 564 {% endif -%} 565 {% endif %} 566 567 {% endfor -%} 568 return res 569 """ 570 renderer = Renderer( 571 TO_DICT, 572 legacy_class_serializer, 573 suppress_coerce=(not type_check.is_coerce()), 574 class_serializer=class_serializer, 575 ) 576 lrenderer = LRenderer(case, serialize_class_var) 577 env = jinja2.Environment(loader=jinja2.DictLoader({"dict": template})) 578 env.filters.update({"rvalue": renderer.render}) 579 env.filters.update({"lvalue": lrenderer.render}) 580 env.filters.update({"case": functools.partial(conv, case=case)}) 581 return env.get_template("dict").render( 582 func=TO_DICT, 583 serde_scope=getattr(cls, SERDE_SCOPE), 584 fields=sefields(cls, serialize_class_var), 585 type_check=type_check, 586 ) 587 588 589def render_union_func( 590 cls: type[Any], union_args: list[type[Any]], tagging: Tagging = DefaultTagging 591) -> str: 592 """ 593 Render function that serializes a field with union type. 594 """ 595 template = """ 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 {"{{t|typename}}": {{t|arg|rvalue}}} 603 604 {% elif tagging.is_internal() and is_taggable(t) %} 605 res = {{t|arg|rvalue}} 606 res["{{tagging.tag}}"] = "{{t|typename}}" 607 return res 608 609 {% elif tagging.is_adjacent() and is_taggable(t) %} 610 res = {"{{tagging.content}}": {{t|arg|rvalue}}} 611 res["{{tagging.tag}}"] = "{{t|typename}}" 612 return res 613 614 {% else %} 615 return {{t|arg|rvalue}} 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 union_name = f"Union[{', '.join([typename(a) for a in union_args])}]" 625 626 renderer = Renderer(TO_DICT, suppress_coerce=True) 627 env = jinja2.Environment(loader=jinja2.DictLoader({"dict": template})) 628 env.filters.update({"arg": lambda x: SeField(x, "obj")}) 629 env.filters.update({"rvalue": renderer.render}) 630 env.filters.update({"typename": typename}) 631 return env.get_template("dict").render( 632 func=union_func_name(UNION_SE_PREFIX, union_args), 633 serde_scope=getattr(cls, SERDE_SCOPE), 634 union_args=union_args, 635 union_name=union_name, 636 tagging=tagging, 637 is_taggable=Tagging.is_taggable, 638 ) 639 640 641@dataclass 642class LRenderer: 643 """ 644 Render lvalue for various types. 645 """ 646 647 case: Optional[str] 648 serialize_class_var: bool = False 649 650 def render(self, arg: SeField[Any]) -> str: 651 """ 652 Render lvalue 653 """ 654 if is_dataclass(arg.type) and arg.flatten: 655 return self.flatten(arg) 656 else: 657 return f'res["{arg.conv_name(self.case)}"]' 658 659 def flatten(self, arg: SeField[Any]) -> str: 660 """ 661 Render field with flatten attribute. 662 """ 663 flattened = [] 664 for f in sefields(arg.type, self.serialize_class_var): 665 flattened.append(self.render(f)) 666 return ", ".join(flattened) 667 668 669@dataclass 670class Renderer: 671 """ 672 Render rvalue for code generation. 673 """ 674 675 func: str 676 legacy_class_serializer: Optional[SerializeFunc] = None 677 suppress_coerce: bool = False 678 """ Suppress type coercing because generated union serializer has its own type checking """ 679 serialize_class_var: bool = False 680 class_serializer: Optional[ClassSerializer] = None 681 682 def render(self, arg: SeField[Any]) -> str: 683 """ 684 Render rvalue 685 686 >>> Renderer(TO_ITER).render(SeField(int, 'i')) 687 'coerce_object(int, i)' 688 689 >>> Renderer(TO_ITER).render(SeField(list[int], 'l')) 690 '[coerce_object(int, v) for v in l]' 691 692 >>> @serialize 693 ... @dataclass(unsafe_hash=True) 694 ... class Foo: 695 ... val: int 696 >>> Renderer(TO_ITER).render(SeField(Foo, 'foo')) 697 "\ 698foo.__serde__.funcs['to_iter'](foo, reuse_instances=reuse_instances, convert_sets=convert_sets)" 699 700 >>> Renderer(TO_ITER).render(SeField(list[Foo], 'foo')) 701 "\ 702[v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, \ 703convert_sets=convert_sets) for v in foo]" 704 705 >>> Renderer(TO_ITER).render(SeField(dict[str, Foo], 'foo')) 706 "\ 707{coerce_object(str, k): v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, \ 708convert_sets=convert_sets) for k, v in foo.items()}" 709 710 >>> Renderer(TO_ITER).render(SeField(dict[Foo, Foo], 'foo')) 711 "\ 712{k.__serde__.funcs['to_iter'](k, reuse_instances=reuse_instances, \ 713convert_sets=convert_sets): v.__serde__.funcs['to_iter'](v, reuse_instances=reuse_instances, \ 714convert_sets=convert_sets) for k, v in foo.items()}" 715 716 >>> Renderer(TO_ITER).render(SeField(tuple[str, Foo, int], 'foo')) 717 "\ 718(coerce_object(str, foo[0]), foo[1].__serde__.funcs['to_iter'](foo[1], \ 719reuse_instances=reuse_instances, convert_sets=convert_sets), \ 720coerce_object(int, foo[2]),)" 721 """ 722 implemented_methods: dict[type[Any], int] = {} 723 class_serializers: Iterable[ClassSerializer] = itertools.chain( 724 GLOBAL_CLASS_SERIALIZER, [self.class_serializer] if self.class_serializer else [] 725 ) 726 for n, class_serializer in enumerate(class_serializers): 727 for sig in class_serializer.__class__.serialize.methods: # type: ignore 728 implemented_methods[sig.types[1]] = n 729 730 custom_serializer_available = arg.type in implemented_methods 731 if custom_serializer_available and not arg.serializer: 732 res = f"class_serializers[{implemented_methods[arg.type]}].serialize({arg.varname})" 733 elif arg.serializer and arg.serializer.inner is not default_serializer: 734 res = self.custom_field_serializer(arg) 735 elif is_dataclass(arg.type): 736 res = self.dataclass(arg) 737 elif is_opt(arg.type): 738 res = self.opt(arg) 739 elif is_list(arg.type): 740 res = self.list(arg) 741 elif is_set(arg.type): 742 res = self.set(arg) 743 elif is_dict(arg.type): 744 res = self.dict(arg) 745 elif is_tuple(arg.type): 746 res = self.tuple(arg) 747 elif is_enum(arg.type): 748 res = self.enum(arg) 749 elif is_numpy_datetime(arg.type): 750 res = serialize_numpy_datetime(arg) 751 elif is_numpy_scalar(arg.type): 752 res = serialize_numpy_scalar(arg) 753 elif is_numpy_array(arg.type): 754 res = serialize_numpy_array(arg) 755 elif is_primitive(arg.type): 756 res = self.primitive(arg) 757 elif is_union(arg.type): 758 res = self.union_func(arg) 759 elif is_str_serializable(arg.type): 760 res = f"{arg.varname} if reuse_instances else {self.string(arg)}" 761 elif is_datetime(arg.type): 762 res = f"{arg.varname} if reuse_instances else {arg.varname}.isoformat()" 763 elif is_none(arg.type): 764 res = "None" 765 elif is_any(arg.type) or is_bearable(arg.type, TypeVar): 766 res = f"to_obj({arg.varname}, True, False, False, c=typing.Any)" 767 elif is_generic(arg.type): 768 origin = get_origin(arg.type) 769 assert origin 770 arg.type = origin 771 res = self.render(arg) 772 elif is_literal(arg.type): 773 res = self.literal(arg) 774 elif is_class_var(arg.type): 775 arg.type = type_args(arg.type)[0] 776 res = self.render(arg) 777 else: 778 res = f"raise_unsupported_type({arg.varname})" 779 780 # Custom field serializer overrides custom class serializer. 781 if self.legacy_class_serializer and not arg.serializer and not custom_serializer_available: 782 return ( 783 "serde_legacy_custom_class_serializer(" 784 f"{typename(arg.type)}, " 785 f"{arg.varname}, " 786 f"default=lambda: {res})" 787 ) 788 else: 789 return res 790 791 def custom_field_serializer(self, arg: SeField[Any]) -> str: 792 """ 793 Render rvalue for the field with custom serializer. 794 """ 795 assert arg.serializer 796 return f"{arg.serializer.name}({arg.varname})" 797 798 def dataclass(self, arg: SeField[Any]) -> str: 799 """ 800 Render rvalue for dataclass. 801 """ 802 if arg.flatten: 803 flattened = [] 804 for f in sefields(arg.type, self.serialize_class_var): 805 f.parent = arg 806 flattened.append(self.render(f)) 807 return ", ".join(flattened) 808 else: 809 return ( 810 f"{arg.varname}.{SERDE_SCOPE}.funcs['{self.func}']({arg.varname}," 811 " reuse_instances=reuse_instances, convert_sets=convert_sets)" 812 ) 813 814 def opt(self, arg: SeField[Any]) -> str: 815 """ 816 Render rvalue for optional. 817 """ 818 if is_bare_opt(arg.type): 819 return f"{arg.varname} if {arg.varname} is not None else None" 820 else: 821 inner = arg[0] 822 inner.name = arg.varname 823 return f"({self.render(inner)}) if {arg.varname} is not None else None" 824 825 def list(self, arg: SeField[Any]) -> str: 826 """ 827 Render rvalue for list. 828 """ 829 if is_bare_list(arg.type): 830 return arg.varname 831 else: 832 earg = arg[0] 833 earg.name = "v" 834 return f"[{self.render(earg)} for v in {arg.varname}]" 835 836 def set(self, arg: SeField[Any]) -> str: 837 """ 838 Render rvalue for set. 839 """ 840 if is_bare_set(arg.type): 841 return f"list({arg.varname}) if convert_sets else {arg.varname}" 842 else: 843 earg = arg[0] 844 earg.name = "v" 845 return ( 846 f"[{self.render(earg)} for v in {arg.varname}] " 847 f"if convert_sets else set({self.render(earg)} for v in {arg.varname})" 848 ) 849 850 def tuple(self, arg: SeField[Any]) -> str: 851 """ 852 Render rvalue for tuple. 853 """ 854 if is_bare_tuple(arg.type): 855 return arg.varname 856 elif is_variable_tuple(arg.type): 857 earg = arg[0] 858 earg.name = "v" 859 return f"tuple({self.render(earg)} for v in {arg.varname})" 860 else: 861 rvalues = [] 862 for i, _ in enumerate(type_args(arg.type)): 863 r = arg[i] 864 r.name = f"{arg.varname}[{i}]" 865 rvalues.append(self.render(r)) 866 return f"({', '.join(rvalues)},)" # trailing , is required for single element tuples 867 868 def dict(self, arg: SeField[Any]) -> str: 869 """ 870 Render rvalue for dict. 871 """ 872 if is_bare_dict(arg.type): 873 return arg.varname 874 else: 875 karg = arg[0] 876 karg.name = "k" 877 varg = arg[1] 878 varg.name = "v" 879 return f"{{{self.render(karg)}: {self.render(varg)} for k, v in {arg.varname}.items()}}" 880 881 def enum(self, arg: SeField[Any]) -> str: 882 return f"enum_value({typename(arg.type)}, {arg.varname})" 883 884 def primitive(self, arg: SeField[Any]) -> str: 885 """ 886 Render rvalue for primitives. 887 """ 888 typ = typename(arg.type) 889 var = arg.varname 890 if self.suppress_coerce: 891 return var 892 else: 893 return f"coerce_object({typ}, {var})" 894 895 def string(self, arg: SeField[Any]) -> str: 896 return f"str({arg.varname})" 897 898 def union_func(self, arg: SeField[Any]) -> str: 899 func_name = union_func_name(UNION_SE_PREFIX, list(type_args(arg.type))) 900 return f"serde_scope.funcs['{func_name}']({arg.varname}, reuse_instances, convert_sets)" 901 902 def literal(self, arg: SeField[Any]) -> str: 903 return f"{arg.varname}" 904 905 906def enum_value(cls: Any, e: Any) -> Any: 907 """ 908 Helper function to get value from enum or enum compatible value. 909 """ 910 if is_enum(e): 911 v = e.value 912 # Recursively get value of Nested enum. 913 if is_enum(v): 914 return enum_value(v.__class__, v) 915 else: 916 return v 917 else: 918 return cls(e).value
@dataclass_transform()
def
serialize( _cls: Optional[type[~T]] = None, rename_all: Optional[str] = None, reuse_instances_default: bool = False, convert_sets_default: bool = False, serializer: Optional[collections.abc.Callable[[type[Any], Any], Any]] = None, tagging: serde.core.Tagging = Tagging(tag=None, content=None, kind=<Kind.External: 1>), type_check: serde.core.TypeCheck = TypeCheck(kind=<Kind.Strict: 3>), serialize_class_var: bool = False, class_serializer: Optional[serde.core.ClassSerializer] = None, **kwargs: Any) -> type[~T]:
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): 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}'
def
is_serializable(instance_or_class: Any) -> bool:
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
def
to_dict( o: Any, c: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None) -> dict[typing.Any, typing.Any]:
434def to_dict( 435 o: Any, 436 c: Optional[type[Any]] = None, 437 reuse_instances: Optional[bool] = None, 438 convert_sets: Optional[bool] = None, 439) -> dict[Any, Any]: 440 """ 441 Serialize object into dictionary. 442 443 >>> @serialize 444 ... class Foo: 445 ... i: int 446 ... s: str = 'foo' 447 ... f: float = 100.0 448 ... b: bool = True 449 >>> 450 >>> to_dict(Foo(i=10)) 451 {'i': 10, 's': 'foo', 'f': 100.0, 'b': True} 452 453 You can pass any type supported by pyserde. For example, 454 455 >>> lst = [Foo(i=10), Foo(i=20)] 456 >>> to_dict(lst) 457 [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}] 458 """ 459 return to_obj( # type: ignore 460 o, named=True, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets 461 )
Serialize object into dictionary.
>>> @serialize
... 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 pass any type supported by pyserde. For example,
>>> 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}]
def
to_tuple( o: Any, c: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None) -> tuple[typing.Any, ...]:
397def to_tuple( 398 o: Any, 399 c: Optional[type[Any]] = None, 400 reuse_instances: Optional[bool] = None, 401 convert_sets: Optional[bool] = None, 402) -> tuple[Any, ...]: 403 """ 404 Serialize object into tuple. 405 406 >>> @serialize 407 ... class Foo: 408 ... i: int 409 ... s: str = 'foo' 410 ... f: float = 100.0 411 ... b: bool = True 412 >>> 413 >>> to_tuple(Foo(i=10)) 414 (10, 'foo', 100.0, True) 415 416 You can pass any type supported by pyserde. For example, 417 418 >>> lst = [Foo(i=10), Foo(i=20)] 419 >>> to_tuple(lst) 420 [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)] 421 """ 422 return to_obj( # type: ignore 423 o, named=False, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets 424 )
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)]