Edit on GitHub

serde.se

This module provides serialize, is_serializable to_dict, to_tuple and classes and functions associated with serialization.

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

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 to dict
  • c: Optional class argument
  • reuse_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. When convert_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}]
def to_tuple( o: Any, c: type[typing.Any] | None = None, reuse_instances: bool | None = None, convert_sets: bool | None = None, skip_none: bool = False) -> tuple[typing.Any, ...]:
450def to_tuple(
451    o: Any,
452    c: type[Any] | None = None,
453    reuse_instances: bool | None = None,
454    convert_sets: bool | None = None,
455    skip_none: bool = False,
456) -> tuple[Any, ...]:
457    """
458    Serialize object into tuple.
459
460    >>> @serialize
461    ... class Foo:
462    ...     i: int
463    ...     s: str = 'foo'
464    ...     f: float = 100.0
465    ...     b: bool = True
466    >>>
467    >>> to_tuple(Foo(i=10))
468    (10, 'foo', 100.0, True)
469
470    You can pass any type supported by pyserde. For example,
471
472    >>> lst = [Foo(i=10), Foo(i=20)]
473    >>> to_tuple(lst)
474    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
475    """
476    return to_obj(  # type: ignore
477        o,
478        named=False,
479        c=c,
480        reuse_instances=reuse_instances,
481        convert_sets=convert_sets,
482        skip_none=skip_none,
483    )

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)]