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

A dataclass with this decorator is serializable into any of the data formats supported by pyserde.

>>> from datetime import datetime
>>> from serde import serialize
>>> from serde.json import to_json
>>>
>>> @serialize
... class Foo:
...     i: int
...     s: str
...     f: float
...     b: bool
>>>
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
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, skip_none: bool = False) -> dict[typing.Any, typing.Any]:
450def to_dict(
451    o: Any,
452    c: Optional[type[Any]] = None,
453    reuse_instances: Optional[bool] = None,
454    convert_sets: Optional[bool] = None,
455    skip_none: bool = False,
456) -> dict[Any, Any]:
457    """
458    Serialize object into python dictionary. This function ensures that the dataclass's fields are
459    accurately represented as key-value pairs in the resulting dictionary.
460
461    * `o`: Any pyserde object that you want to convert to `dict`
462    * `c`: Optional class argument
463    * `reuse_instances`: pyserde will pass instances (e.g. Path, datetime) directly to serializer
464    instead of converting them to serializable representation e.g. string. This behaviour allows
465    to delegate serializtation to underlying data format packages e.g. `pyyaml` and potentially
466    improve performance.
467    * `convert_sets`: This option controls how sets are handled during serialization and
468    deserialization. When `convert_sets` is set to True, pyserde will convert sets to lists during
469    serialization and back to sets during deserialization. This is useful for data formats that
470    do not natively support sets.
471    * `skip_none`: When set to True, any field in the class with a None value is excluded from the
472    serialized output. Defaults to False.
473
474    >>> from serde import serde
475    >>> @serde
476    ... class Foo:
477    ...     i: int
478    ...     s: str = 'foo'
479    ...     f: float = 100.0
480    ...     b: bool = True
481    >>>
482    >>> to_dict(Foo(i=10))
483    {'i': 10, 's': 'foo', 'f': 100.0, 'b': True}
484
485    You can serialize not only pyserde objects but also objects of any supported types. For example,
486    the following example serializes list of pyserde objects into dict.
487
488    >>> lst = [Foo(i=10), Foo(i=20)]
489    >>> to_dict(lst)
490    [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
491    """
492    return to_obj(  # type: ignore
493        o,
494        named=True,
495        c=c,
496        reuse_instances=reuse_instances,
497        convert_sets=convert_sets,
498        skip_none=skip_none,
499    )

Serialize object into python dictionary. This function ensures that the dataclass's fields are accurately represented as key-value pairs in the resulting dictionary.

  • o: Any pyserde object that you want to convert 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: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None, skip_none: bool = False) -> tuple[typing.Any, ...]:
407def to_tuple(
408    o: Any,
409    c: Optional[type[Any]] = None,
410    reuse_instances: Optional[bool] = None,
411    convert_sets: Optional[bool] = None,
412    skip_none: bool = False,
413) -> tuple[Any, ...]:
414    """
415    Serialize object into tuple.
416
417    >>> @serialize
418    ... class Foo:
419    ...     i: int
420    ...     s: str = 'foo'
421    ...     f: float = 100.0
422    ...     b: bool = True
423    >>>
424    >>> to_tuple(Foo(i=10))
425    (10, 'foo', 100.0, True)
426
427    You can pass any type supported by pyserde. For example,
428
429    >>> lst = [Foo(i=10), Foo(i=20)]
430    >>> to_tuple(lst)
431    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
432    """
433    return to_obj(  # type: ignore
434        o,
435        named=False,
436        c=c,
437        reuse_instances=reuse_instances,
438        convert_sets=convert_sets,
439        skip_none=skip_none,
440    )

Serialize object into tuple.

>>> @serialize
... class Foo:
...     i: int
...     s: str = 'foo'
...     f: float = 100.0
...     b: bool = True
>>>
>>> to_tuple(Foo(i=10))
(10, 'foo', 100.0, True)

You can pass any type supported by pyserde. For example,

>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_tuple(lst)
[(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]