docs for muutils v0.8.10
View Source on GitHub

muutils.json_serialize.serializable_field

extends dataclasses.Field for use with SerializableDataclass

In particular, instead of using dataclasses.field, use serializable_field to define fields in a SerializableDataclass. You provide information on how the field should be serialized and loaded (as well as anything that goes into dataclasses.field) when you define the field, and the SerializableDataclass will automatically use those functions.


  1"""extends `dataclasses.Field` for use with `SerializableDataclass`
  2
  3In particular, instead of using `dataclasses.field`, use `serializable_field` to define fields in a `SerializableDataclass`.
  4You provide information on how the field should be serialized and loaded (as well as anything that goes into `dataclasses.field`)
  5when you define the field, and the `SerializableDataclass` will automatically use those functions.
  6
  7"""
  8
  9from __future__ import annotations
 10
 11import dataclasses
 12import sys
 13import types
 14from typing import Any, Callable, Optional, Union, overload, TypeVar
 15
 16
 17# pylint: disable=bad-mcs-classmethod-argument, too-many-arguments, protected-access
 18
 19
 20class SerializableField(dataclasses.Field):
 21    """extension of `dataclasses.Field` with additional serialization properties"""
 22
 23    __slots__ = (
 24        # from dataclasses.Field.__slots__
 25        "name",
 26        "type",
 27        "default",
 28        "default_factory",
 29        "repr",
 30        "hash",
 31        "init",
 32        "compare",
 33        "metadata",
 34        "kw_only",
 35        "_field_type",  # Private: not to be used by user code.
 36        # new ones
 37        "serialize",
 38        "serialization_fn",
 39        "loading_fn",
 40        "deserialize_fn",  # new alternative to loading_fn
 41        "assert_type",
 42        "custom_typecheck_fn",
 43    )
 44
 45    def __init__(
 46        self,
 47        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 48        default_factory: Union[
 49            Callable[[], Any], dataclasses._MISSING_TYPE
 50        ] = dataclasses.MISSING,
 51        init: bool = True,
 52        repr: bool = True,
 53        hash: Optional[bool] = None,
 54        compare: bool = True,
 55        # TODO: add field for custom comparator (such as serializing)
 56        metadata: Optional[types.MappingProxyType] = None,
 57        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 58        serialize: bool = True,
 59        serialization_fn: Optional[Callable[[Any], Any]] = None,
 60        loading_fn: Optional[Callable[[Any], Any]] = None,
 61        deserialize_fn: Optional[Callable[[Any], Any]] = None,
 62        assert_type: bool = True,
 63        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
 64    ):
 65        # TODO: should we do this check, or assume the user knows what they are doing?
 66        if init and not serialize:
 67            raise ValueError("Cannot have init=True and serialize=False")
 68
 69        # need to assemble kwargs in this hacky way so as not to upset type checking
 70        super_kwargs: dict[str, Any] = dict(
 71            default=default,
 72            default_factory=default_factory,
 73            init=init,
 74            repr=repr,
 75            hash=hash,
 76            compare=compare,
 77            kw_only=kw_only,
 78        )
 79
 80        if metadata is not None:
 81            super_kwargs["metadata"] = metadata
 82        else:
 83            super_kwargs["metadata"] = types.MappingProxyType({})
 84
 85        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
 86        if sys.version_info < (3, 10):
 87            if super_kwargs["kw_only"] == True:  # noqa: E712
 88                raise ValueError("kw_only is not supported in python >=3.9")
 89            else:
 90                del super_kwargs["kw_only"]
 91
 92        # actually init the super class
 93        super().__init__(**super_kwargs)  # type: ignore[call-arg]
 94
 95        # now init the new fields
 96        self.serialize: bool = serialize
 97        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
 98
 99        if loading_fn is not None and deserialize_fn is not None:
100            raise ValueError(
101                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
102                "`loading_fn` is the older interface and takes the dict of the class, ",
103                "`deserialize_fn` is the new interface and takes only the field's value.",
104            )
105        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
106        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
107
108        self.assert_type: bool = assert_type
109        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
110
111    @classmethod
112    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
113        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
114        return cls(
115            default=field.default,
116            default_factory=field.default_factory,
117            init=field.init,
118            repr=field.repr,
119            hash=field.hash,
120            compare=field.compare,
121            metadata=field.metadata,
122            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
123            serialize=field.repr,  # serialize if it's going to be repr'd
124            serialization_fn=None,
125            loading_fn=None,
126            deserialize_fn=None,
127        )
128
129
130Sfield_T = TypeVar("Sfield_T")
131
132
133@overload
134def serializable_field(
135    *_args,
136    default_factory: Callable[[], Sfield_T],
137    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
138    init: bool = True,
139    repr: bool = True,
140    hash: Optional[bool] = None,
141    compare: bool = True,
142    metadata: Optional[types.MappingProxyType] = None,
143    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
144    serialize: bool = True,
145    serialization_fn: Optional[Callable[[Any], Any]] = None,
146    deserialize_fn: Optional[Callable[[Any], Any]] = None,
147    assert_type: bool = True,
148    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
149    **kwargs: Any,
150) -> Sfield_T: ...
151@overload
152def serializable_field(
153    *_args,
154    default: Sfield_T,
155    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
156    init: bool = True,
157    repr: bool = True,
158    hash: Optional[bool] = None,
159    compare: bool = True,
160    metadata: Optional[types.MappingProxyType] = None,
161    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
162    serialize: bool = True,
163    serialization_fn: Optional[Callable[[Any], Any]] = None,
164    deserialize_fn: Optional[Callable[[Any], Any]] = None,
165    assert_type: bool = True,
166    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
167    **kwargs: Any,
168) -> Sfield_T: ...
169@overload
170def serializable_field(
171    *_args,
172    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
173    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
174    init: bool = True,
175    repr: bool = True,
176    hash: Optional[bool] = None,
177    compare: bool = True,
178    metadata: Optional[types.MappingProxyType] = None,
179    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
180    serialize: bool = True,
181    serialization_fn: Optional[Callable[[Any], Any]] = None,
182    deserialize_fn: Optional[Callable[[Any], Any]] = None,
183    assert_type: bool = True,
184    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
185    **kwargs: Any,
186) -> Any: ...
187def serializable_field(
188    *_args,
189    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
190    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
191    init: bool = True,
192    repr: bool = True,
193    hash: Optional[bool] = None,
194    compare: bool = True,
195    metadata: Optional[types.MappingProxyType] = None,
196    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
197    serialize: bool = True,
198    serialization_fn: Optional[Callable[[Any], Any]] = None,
199    deserialize_fn: Optional[Callable[[Any], Any]] = None,
200    assert_type: bool = True,
201    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
202    **kwargs: Any,
203) -> Any:
204    """Create a new `SerializableField`
205
206    ```
207    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
208    default_factory: Callable[[], Sfield_T]
209    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
210    init: bool = True,
211    repr: bool = True,
212    hash: Optional[bool] = None,
213    compare: bool = True,
214    metadata: types.MappingProxyType | None = None,
215    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
216    # ----------------------------------------------------------------------
217    # new in `SerializableField`, not in `dataclasses.Field`
218    serialize: bool = True,
219    serialization_fn: Optional[Callable[[Any], Any]] = None,
220    loading_fn: Optional[Callable[[Any], Any]] = None,
221    deserialize_fn: Optional[Callable[[Any], Any]] = None,
222    assert_type: bool = True,
223    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
224    ```
225
226    # new Parameters:
227    - `serialize`: whether to serialize this field when serializing the class'
228    - `serialization_fn`: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the `SerializerHandler`s defined in `muutils.json_serialize.json_serialize`
229    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
230    - `deserialize_fn`: new alternative to `loading_fn`. takes only the field's value, not the whole class. if both `loading_fn` and `deserialize_fn` are provided, an error will be raised.
231    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
232    - `custom_typecheck_fn`: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.
233
234    # Gotchas:
235    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
236
237    ```python
238    class MyClass:
239        my_field: int = serializable_field(
240            serialization_fn=lambda x: str(x),
241            loading_fn=lambda x["my_field"]: int(x)
242        )
243    ```
244
245    using `deserialize_fn` instead:
246
247    ```python
248    class MyClass:
249        my_field: int = serializable_field(
250            serialization_fn=lambda x: str(x),
251            deserialize_fn=lambda x: int(x)
252        )
253    ```
254
255    In the above code, `my_field` is an int but will be serialized as a string.
256
257    note that if not using ZANJ, and you have a class inside a container, you MUST provide
258    `serialization_fn` and `loading_fn` to serialize and load the container.
259    ZANJ will automatically do this for you.
260
261    # TODO: `custom_value_check_fn`: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test
262    """
263    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
264    return SerializableField(
265        default=default,
266        default_factory=default_factory,
267        init=init,
268        repr=repr,
269        hash=hash,
270        compare=compare,
271        metadata=metadata,
272        kw_only=kw_only,
273        serialize=serialize,
274        serialization_fn=serialization_fn,
275        deserialize_fn=deserialize_fn,
276        assert_type=assert_type,
277        custom_typecheck_fn=custom_typecheck_fn,
278        **kwargs,
279    )

class SerializableField(dataclasses.Field):
 21class SerializableField(dataclasses.Field):
 22    """extension of `dataclasses.Field` with additional serialization properties"""
 23
 24    __slots__ = (
 25        # from dataclasses.Field.__slots__
 26        "name",
 27        "type",
 28        "default",
 29        "default_factory",
 30        "repr",
 31        "hash",
 32        "init",
 33        "compare",
 34        "metadata",
 35        "kw_only",
 36        "_field_type",  # Private: not to be used by user code.
 37        # new ones
 38        "serialize",
 39        "serialization_fn",
 40        "loading_fn",
 41        "deserialize_fn",  # new alternative to loading_fn
 42        "assert_type",
 43        "custom_typecheck_fn",
 44    )
 45
 46    def __init__(
 47        self,
 48        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 49        default_factory: Union[
 50            Callable[[], Any], dataclasses._MISSING_TYPE
 51        ] = dataclasses.MISSING,
 52        init: bool = True,
 53        repr: bool = True,
 54        hash: Optional[bool] = None,
 55        compare: bool = True,
 56        # TODO: add field for custom comparator (such as serializing)
 57        metadata: Optional[types.MappingProxyType] = None,
 58        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 59        serialize: bool = True,
 60        serialization_fn: Optional[Callable[[Any], Any]] = None,
 61        loading_fn: Optional[Callable[[Any], Any]] = None,
 62        deserialize_fn: Optional[Callable[[Any], Any]] = None,
 63        assert_type: bool = True,
 64        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
 65    ):
 66        # TODO: should we do this check, or assume the user knows what they are doing?
 67        if init and not serialize:
 68            raise ValueError("Cannot have init=True and serialize=False")
 69
 70        # need to assemble kwargs in this hacky way so as not to upset type checking
 71        super_kwargs: dict[str, Any] = dict(
 72            default=default,
 73            default_factory=default_factory,
 74            init=init,
 75            repr=repr,
 76            hash=hash,
 77            compare=compare,
 78            kw_only=kw_only,
 79        )
 80
 81        if metadata is not None:
 82            super_kwargs["metadata"] = metadata
 83        else:
 84            super_kwargs["metadata"] = types.MappingProxyType({})
 85
 86        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
 87        if sys.version_info < (3, 10):
 88            if super_kwargs["kw_only"] == True:  # noqa: E712
 89                raise ValueError("kw_only is not supported in python >=3.9")
 90            else:
 91                del super_kwargs["kw_only"]
 92
 93        # actually init the super class
 94        super().__init__(**super_kwargs)  # type: ignore[call-arg]
 95
 96        # now init the new fields
 97        self.serialize: bool = serialize
 98        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
 99
100        if loading_fn is not None and deserialize_fn is not None:
101            raise ValueError(
102                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
103                "`loading_fn` is the older interface and takes the dict of the class, ",
104                "`deserialize_fn` is the new interface and takes only the field's value.",
105            )
106        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
107        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
108
109        self.assert_type: bool = assert_type
110        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
111
112    @classmethod
113    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
114        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
115        return cls(
116            default=field.default,
117            default_factory=field.default_factory,
118            init=field.init,
119            repr=field.repr,
120            hash=field.hash,
121            compare=field.compare,
122            metadata=field.metadata,
123            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
124            serialize=field.repr,  # serialize if it's going to be repr'd
125            serialization_fn=None,
126            loading_fn=None,
127            deserialize_fn=None,
128        )

extension of dataclasses.Field with additional serialization properties

SerializableField( default: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, default_factory: Union[Callable[[], Any], dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, init: bool = True, repr: bool = True, hash: Optional[bool] = None, compare: bool = True, metadata: Optional[mappingproxy] = None, kw_only: Union[bool, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, serialize: bool = True, serialization_fn: Optional[Callable[[Any], Any]] = None, loading_fn: Optional[Callable[[Any], Any]] = None, deserialize_fn: Optional[Callable[[Any], Any]] = None, assert_type: bool = True, custom_typecheck_fn: Optional[Callable[[<member 'type' of 'SerializableField' objects>], bool]] = None)
 46    def __init__(
 47        self,
 48        default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 49        default_factory: Union[
 50            Callable[[], Any], dataclasses._MISSING_TYPE
 51        ] = dataclasses.MISSING,
 52        init: bool = True,
 53        repr: bool = True,
 54        hash: Optional[bool] = None,
 55        compare: bool = True,
 56        # TODO: add field for custom comparator (such as serializing)
 57        metadata: Optional[types.MappingProxyType] = None,
 58        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 59        serialize: bool = True,
 60        serialization_fn: Optional[Callable[[Any], Any]] = None,
 61        loading_fn: Optional[Callable[[Any], Any]] = None,
 62        deserialize_fn: Optional[Callable[[Any], Any]] = None,
 63        assert_type: bool = True,
 64        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
 65    ):
 66        # TODO: should we do this check, or assume the user knows what they are doing?
 67        if init and not serialize:
 68            raise ValueError("Cannot have init=True and serialize=False")
 69
 70        # need to assemble kwargs in this hacky way so as not to upset type checking
 71        super_kwargs: dict[str, Any] = dict(
 72            default=default,
 73            default_factory=default_factory,
 74            init=init,
 75            repr=repr,
 76            hash=hash,
 77            compare=compare,
 78            kw_only=kw_only,
 79        )
 80
 81        if metadata is not None:
 82            super_kwargs["metadata"] = metadata
 83        else:
 84            super_kwargs["metadata"] = types.MappingProxyType({})
 85
 86        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
 87        if sys.version_info < (3, 10):
 88            if super_kwargs["kw_only"] == True:  # noqa: E712
 89                raise ValueError("kw_only is not supported in python >=3.9")
 90            else:
 91                del super_kwargs["kw_only"]
 92
 93        # actually init the super class
 94        super().__init__(**super_kwargs)  # type: ignore[call-arg]
 95
 96        # now init the new fields
 97        self.serialize: bool = serialize
 98        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
 99
100        if loading_fn is not None and deserialize_fn is not None:
101            raise ValueError(
102                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
103                "`loading_fn` is the older interface and takes the dict of the class, ",
104                "`deserialize_fn` is the new interface and takes only the field's value.",
105            )
106        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
107        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
108
109        self.assert_type: bool = assert_type
110        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
serialize: bool
serialization_fn: Optional[Callable[[Any], Any]]
loading_fn: Optional[Callable[[Any], Any]]
deserialize_fn: Optional[Callable[[Any], Any]]
assert_type: bool
custom_typecheck_fn: Optional[Callable[[<member 'type' of 'SerializableField' objects>], bool]]
@classmethod
def from_Field( cls, field: dataclasses.Field) -> SerializableField:
112    @classmethod
113    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
114        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
115        return cls(
116            default=field.default,
117            default_factory=field.default_factory,
118            init=field.init,
119            repr=field.repr,
120            hash=field.hash,
121            compare=field.compare,
122            metadata=field.metadata,
123            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
124            serialize=field.repr,  # serialize if it's going to be repr'd
125            serialization_fn=None,
126            loading_fn=None,
127            deserialize_fn=None,
128        )

copy all values from a dataclasses.Field to new SerializableField

name
type
default
default_factory
init
repr
hash
compare
metadata
kw_only
def serializable_field( *_args, default: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, default_factory: Union[Any, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, init: bool = True, repr: bool = True, hash: Optional[bool] = None, compare: bool = True, metadata: Optional[mappingproxy] = None, kw_only: Union[bool, dataclasses._MISSING_TYPE] = <dataclasses._MISSING_TYPE object>, serialize: bool = True, serialization_fn: Optional[Callable[[Any], Any]] = None, deserialize_fn: Optional[Callable[[Any], Any]] = None, assert_type: bool = True, custom_typecheck_fn: Optional[Callable[[type], bool]] = None, **kwargs: Any) -> Any:
188def serializable_field(
189    *_args,
190    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
191    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
192    init: bool = True,
193    repr: bool = True,
194    hash: Optional[bool] = None,
195    compare: bool = True,
196    metadata: Optional[types.MappingProxyType] = None,
197    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
198    serialize: bool = True,
199    serialization_fn: Optional[Callable[[Any], Any]] = None,
200    deserialize_fn: Optional[Callable[[Any], Any]] = None,
201    assert_type: bool = True,
202    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
203    **kwargs: Any,
204) -> Any:
205    """Create a new `SerializableField`
206
207    ```
208    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
209    default_factory: Callable[[], Sfield_T]
210    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
211    init: bool = True,
212    repr: bool = True,
213    hash: Optional[bool] = None,
214    compare: bool = True,
215    metadata: types.MappingProxyType | None = None,
216    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
217    # ----------------------------------------------------------------------
218    # new in `SerializableField`, not in `dataclasses.Field`
219    serialize: bool = True,
220    serialization_fn: Optional[Callable[[Any], Any]] = None,
221    loading_fn: Optional[Callable[[Any], Any]] = None,
222    deserialize_fn: Optional[Callable[[Any], Any]] = None,
223    assert_type: bool = True,
224    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
225    ```
226
227    # new Parameters:
228    - `serialize`: whether to serialize this field when serializing the class'
229    - `serialization_fn`: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the `SerializerHandler`s defined in `muutils.json_serialize.json_serialize`
230    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
231    - `deserialize_fn`: new alternative to `loading_fn`. takes only the field's value, not the whole class. if both `loading_fn` and `deserialize_fn` are provided, an error will be raised.
232    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
233    - `custom_typecheck_fn`: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.
234
235    # Gotchas:
236    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
237
238    ```python
239    class MyClass:
240        my_field: int = serializable_field(
241            serialization_fn=lambda x: str(x),
242            loading_fn=lambda x["my_field"]: int(x)
243        )
244    ```
245
246    using `deserialize_fn` instead:
247
248    ```python
249    class MyClass:
250        my_field: int = serializable_field(
251            serialization_fn=lambda x: str(x),
252            deserialize_fn=lambda x: int(x)
253        )
254    ```
255
256    In the above code, `my_field` is an int but will be serialized as a string.
257
258    note that if not using ZANJ, and you have a class inside a container, you MUST provide
259    `serialization_fn` and `loading_fn` to serialize and load the container.
260    ZANJ will automatically do this for you.
261
262    # TODO: `custom_value_check_fn`: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test
263    """
264    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
265    return SerializableField(
266        default=default,
267        default_factory=default_factory,
268        init=init,
269        repr=repr,
270        hash=hash,
271        compare=compare,
272        metadata=metadata,
273        kw_only=kw_only,
274        serialize=serialize,
275        serialization_fn=serialization_fn,
276        deserialize_fn=deserialize_fn,
277        assert_type=assert_type,
278        custom_typecheck_fn=custom_typecheck_fn,
279        **kwargs,
280    )

Create a new SerializableField

default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
default_factory: Callable[[], Sfield_T]
| dataclasses._MISSING_TYPE = dataclasses.MISSING,
init: bool = True,
repr: bool = True,
hash: Optional[bool] = None,
compare: bool = True,
metadata: types.MappingProxyType | None = None,
kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
# ----------------------------------------------------------------------
# new in `SerializableField`, not in `dataclasses.Field`
serialize: bool = True,
serialization_fn: Optional[Callable[[Any], Any]] = None,
loading_fn: Optional[Callable[[Any], Any]] = None,
deserialize_fn: Optional[Callable[[Any], Any]] = None,
assert_type: bool = True,
custom_typecheck_fn: Optional[Callable[[type], bool]] = None,

new Parameters:

  • serialize: whether to serialize this field when serializing the class'
  • serialization_fn: function taking the instance of the field and returning a serializable object. If not provided, will iterate through the SerializerHandlers defined in muutils.json_serialize.json_serialize
  • loading_fn: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
  • deserialize_fn: new alternative to loading_fn. takes only the field's value, not the whole class. if both loading_fn and deserialize_fn are provided, an error will be raised.
  • assert_type: whether to assert the type of the field when loading. if False, will not check the type of the field.
  • custom_typecheck_fn: function taking the type of the field and returning whether the type itself is valid. if not provided, will use the default type checking.

Gotchas:

  • loading_fn takes the dict of the class, not the field. if you wanted a loading_fn that does nothing, you'd write:
class MyClass:
    my_field: int = serializable_field(
        serialization_fn=lambda x: str(x),
        loading_fn=lambda x["my_field"]: int(x)
    )

using deserialize_fn instead:

class MyClass:
    my_field: int = serializable_field(
        serialization_fn=lambda x: str(x),
        deserialize_fn=lambda x: int(x)
    )

In the above code, my_field is an int but will be serialized as a string.

note that if not using ZANJ, and you have a class inside a container, you MUST provide serialization_fn and loading_fn to serialize and load the container. ZANJ will automatically do this for you.

TODO: custom_value_check_fn: function taking the value of the field and returning whether the value itself is valid. if not provided, any value is valid as long as it passes the type test