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

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

>>> from datetime import datetime
>>> from serde import serialize
>>> from serde.json import to_json
>>>
>>> @serialize
... class Foo:
...     i: int
...     s: str
...     f: float
...     b: bool
>>>
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
def is_serializable(instance_or_class: Any) -> bool:
303def is_serializable(instance_or_class: Any) -> bool:
304    """
305    Test if an instance or class is serializable.
306
307    >>> @serialize
308    ... class Foo:
309    ...     pass
310
311    Testing `Foo` class object returns `True`.
312    >>> is_serializable(Foo)
313    True
314
315    Testing `Foo` object also returns `True`.
316    >>> is_serializable(Foo())
317    True
318    """
319    return hasattr(instance_or_class, SERDE_SCOPE)

Test if an instance or class is serializable.

>>> @serialize
... class Foo:
...     pass

Testing Foo class object returns True.

>>> is_serializable(Foo)
True

Testing Foo object also returns True.

>>> is_serializable(Foo())
True
def to_dict( o: Any, c: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None) -> dict[typing.Any, typing.Any]:
434def to_dict(
435    o: Any,
436    c: Optional[type[Any]] = None,
437    reuse_instances: Optional[bool] = None,
438    convert_sets: Optional[bool] = None,
439) -> dict[Any, Any]:
440    """
441    Serialize object into dictionary.
442
443    >>> @serialize
444    ... class Foo:
445    ...     i: int
446    ...     s: str = 'foo'
447    ...     f: float = 100.0
448    ...     b: bool = True
449    >>>
450    >>> to_dict(Foo(i=10))
451    {'i': 10, 's': 'foo', 'f': 100.0, 'b': True}
452
453    You can pass any type supported by pyserde. For example,
454
455    >>> lst = [Foo(i=10), Foo(i=20)]
456    >>> to_dict(lst)
457    [{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
458    """
459    return to_obj(  # type: ignore
460        o, named=True, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets
461    )

Serialize object into dictionary.

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

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

>>> lst = [Foo(i=10), Foo(i=20)]
>>> to_dict(lst)
[{'i': 10, 's': 'foo', 'f': 100.0, 'b': True}, {'i': 20, 's': 'foo', 'f': 100.0, 'b': True}]
def to_tuple( o: Any, c: Optional[type[Any]] = None, reuse_instances: Optional[bool] = None, convert_sets: Optional[bool] = None) -> tuple[typing.Any, ...]:
397def to_tuple(
398    o: Any,
399    c: Optional[type[Any]] = None,
400    reuse_instances: Optional[bool] = None,
401    convert_sets: Optional[bool] = None,
402) -> tuple[Any, ...]:
403    """
404    Serialize object into tuple.
405
406    >>> @serialize
407    ... class Foo:
408    ...     i: int
409    ...     s: str = 'foo'
410    ...     f: float = 100.0
411    ...     b: bool = True
412    >>>
413    >>> to_tuple(Foo(i=10))
414    (10, 'foo', 100.0, True)
415
416    You can pass any type supported by pyserde. For example,
417
418    >>> lst = [Foo(i=10), Foo(i=20)]
419    >>> to_tuple(lst)
420    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
421    """
422    return to_obj(  # type: ignore
423        o, named=False, c=c, reuse_instances=reuse_instances, convert_sets=convert_sets
424    )

Serialize object into tuple.

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

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

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