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

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

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