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

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, ...]:
427def to_tuple(
428    o: Any,
429    c: Optional[type[Any]] = None,
430    reuse_instances: Optional[bool] = None,
431    convert_sets: Optional[bool] = None,
432    skip_none: bool = False,
433) -> tuple[Any, ...]:
434    """
435    Serialize object into tuple.
436
437    >>> @serialize
438    ... class Foo:
439    ...     i: int
440    ...     s: str = 'foo'
441    ...     f: float = 100.0
442    ...     b: bool = True
443    >>>
444    >>> to_tuple(Foo(i=10))
445    (10, 'foo', 100.0, True)
446
447    You can pass any type supported by pyserde. For example,
448
449    >>> lst = [Foo(i=10), Foo(i=20)]
450    >>> to_tuple(lst)
451    [(10, 'foo', 100.0, True), (20, 'foo', 100.0, True)]
452    """
453    return to_obj(  # type: ignore
454        o,
455        named=False,
456        c=c,
457        reuse_instances=reuse_instances,
458        convert_sets=convert_sets,
459        skip_none=skip_none,
460    )

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