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

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

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

Serialize object into tuple.

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

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

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