docs for muutils v0.8.12
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        "doc",
 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        doc: str | None = None,
 57        # TODO: add field for custom comparator (such as serializing)
 58        metadata: Optional[types.MappingProxyType] = None,
 59        kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
 60        serialize: bool = True,
 61        serialization_fn: Optional[Callable[[Any], Any]] = None,
 62        loading_fn: Optional[Callable[[Any], Any]] = None,
 63        deserialize_fn: Optional[Callable[[Any], Any]] = None,
 64        assert_type: bool = True,
 65        custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
 66    ):
 67        # TODO: should we do this check, or assume the user knows what they are doing?
 68        if init and not serialize:
 69            raise ValueError("Cannot have init=True and serialize=False")
 70
 71        # need to assemble kwargs in this hacky way so as not to upset type checking
 72        super_kwargs: dict[str, Any] = dict(
 73            default=default,
 74            default_factory=default_factory,
 75            init=init,
 76            repr=repr,
 77            hash=hash,
 78            compare=compare,
 79            kw_only=kw_only,
 80        )
 81
 82        if metadata is not None:
 83            super_kwargs["metadata"] = metadata
 84        else:
 85            super_kwargs["metadata"] = types.MappingProxyType({})
 86
 87        # only pass `doc` to super if python >=3.14
 88        if sys.version_info >= (3, 14):
 89            super_kwargs["doc"] = doc
 90
 91        # special check, kw_only is not supported in python <3.9 and `dataclasses.MISSING` is truthy
 92        if sys.version_info < (3, 10):
 93            if super_kwargs["kw_only"] == True:  # noqa: E712
 94                raise ValueError("kw_only is not supported in python >=3.9")
 95            else:
 96                del super_kwargs["kw_only"]
 97
 98        # actually init the super class
 99        super().__init__(**super_kwargs)  # type: ignore[call-arg]
100
101        # init doc if python <3.14
102        if sys.version_info < (3, 14):
103            self.doc: str | None = doc
104
105        # now init the new fields
106        self.serialize: bool = serialize
107        self.serialization_fn: Optional[Callable[[Any], Any]] = serialization_fn
108
109        if loading_fn is not None and deserialize_fn is not None:
110            raise ValueError(
111                "Cannot pass both loading_fn and deserialize_fn, pass only one. ",
112                "`loading_fn` is the older interface and takes the dict of the class, ",
113                "`deserialize_fn` is the new interface and takes only the field's value.",
114            )
115        self.loading_fn: Optional[Callable[[Any], Any]] = loading_fn
116        self.deserialize_fn: Optional[Callable[[Any], Any]] = deserialize_fn
117
118        self.assert_type: bool = assert_type
119        self.custom_typecheck_fn: Optional[Callable[[type], bool]] = custom_typecheck_fn
120
121    @classmethod
122    def from_Field(cls, field: dataclasses.Field) -> "SerializableField":
123        """copy all values from a `dataclasses.Field` to new `SerializableField`"""
124        return cls(
125            default=field.default,
126            default_factory=field.default_factory,
127            init=field.init,
128            repr=field.repr,
129            hash=field.hash,
130            compare=field.compare,
131            doc=getattr(field, "doc", None),  # `doc` added in python <3.14
132            metadata=field.metadata,
133            kw_only=getattr(field, "kw_only", dataclasses.MISSING),  # for python <3.9
134            serialize=field.repr,  # serialize if it's going to be repr'd
135            serialization_fn=None,
136            loading_fn=None,
137            deserialize_fn=None,
138        )
139
140
141Sfield_T = TypeVar("Sfield_T")
142
143
144@overload
145def serializable_field(  # only `default_factory` is provided
146    *_args,
147    default_factory: Callable[[], Sfield_T],
148    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
149    init: bool = True,
150    repr: bool = True,
151    hash: Optional[bool] = None,
152    compare: bool = True,
153    doc: str | None = None,
154    metadata: Optional[types.MappingProxyType] = None,
155    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
156    serialize: bool = True,
157    serialization_fn: Optional[Callable[[Any], Any]] = None,
158    deserialize_fn: Optional[Callable[[Any], Any]] = None,
159    assert_type: bool = True,
160    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
161    **kwargs: Any,
162) -> Sfield_T: ...
163@overload
164def serializable_field(  # only `default` is provided
165    *_args,
166    default: Sfield_T,
167    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
168    init: bool = True,
169    repr: bool = True,
170    hash: Optional[bool] = None,
171    compare: bool = True,
172    doc: str | None = None,
173    metadata: Optional[types.MappingProxyType] = None,
174    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
175    serialize: bool = True,
176    serialization_fn: Optional[Callable[[Any], Any]] = None,
177    deserialize_fn: Optional[Callable[[Any], Any]] = None,
178    assert_type: bool = True,
179    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
180    **kwargs: Any,
181) -> Sfield_T: ...
182@overload
183def serializable_field(  # both `default` and `default_factory` are MISSING
184    *_args,
185    default: dataclasses._MISSING_TYPE = dataclasses.MISSING,
186    default_factory: dataclasses._MISSING_TYPE = dataclasses.MISSING,
187    init: bool = True,
188    repr: bool = True,
189    hash: Optional[bool] = None,
190    compare: bool = True,
191    doc: str | None = None,
192    metadata: Optional[types.MappingProxyType] = None,
193    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
194    serialize: bool = True,
195    serialization_fn: Optional[Callable[[Any], Any]] = None,
196    deserialize_fn: Optional[Callable[[Any], Any]] = None,
197    assert_type: bool = True,
198    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
199    **kwargs: Any,
200) -> Any: ...
201def serializable_field(  # general implementation
202    *_args,
203    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
204    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
205    init: bool = True,
206    repr: bool = True,
207    hash: Optional[bool] = None,
208    compare: bool = True,
209    doc: str | None = None,
210    metadata: Optional[types.MappingProxyType] = None,
211    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
212    serialize: bool = True,
213    serialization_fn: Optional[Callable[[Any], Any]] = None,
214    deserialize_fn: Optional[Callable[[Any], Any]] = None,
215    assert_type: bool = True,
216    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
217    **kwargs: Any,
218) -> Any:
219    """Create a new `SerializableField`
220
221    ```
222    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
223    default_factory: Callable[[], Sfield_T]
224    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
225    init: bool = True,
226    repr: bool = True,
227    hash: Optional[bool] = None,
228    compare: bool = True,
229    doc: str | None = None, # new in python 3.14. can alternately pass `description` to match pydantic, but this is discouraged
230    metadata: types.MappingProxyType | None = None,
231    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
232    # ----------------------------------------------------------------------
233    # new in `SerializableField`, not in `dataclasses.Field`
234    serialize: bool = True,
235    serialization_fn: Optional[Callable[[Any], Any]] = None,
236    loading_fn: Optional[Callable[[Any], Any]] = None,
237    deserialize_fn: Optional[Callable[[Any], Any]] = None,
238    assert_type: bool = True,
239    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
240    ```
241
242    # new Parameters:
243    - `serialize`: whether to serialize this field when serializing the class'
244    - `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`
245    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
246    - `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.
247    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
248    - `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.
249
250    # Gotchas:
251    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
252
253    ```python
254    class MyClass:
255        my_field: int = serializable_field(
256            serialization_fn=lambda x: str(x),
257            loading_fn=lambda x["my_field"]: int(x)
258        )
259    ```
260
261    using `deserialize_fn` instead:
262
263    ```python
264    class MyClass:
265        my_field: int = serializable_field(
266            serialization_fn=lambda x: str(x),
267            deserialize_fn=lambda x: int(x)
268        )
269    ```
270
271    In the above code, `my_field` is an int but will be serialized as a string.
272
273    note that if not using ZANJ, and you have a class inside a container, you MUST provide
274    `serialization_fn` and `loading_fn` to serialize and load the container.
275    ZANJ will automatically do this for you.
276
277    # 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
278    """
279    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
280
281    if "description" in kwargs:
282        import warnings
283
284        warnings.warn(
285            "`description` is deprecated, use `doc` instead",
286            DeprecationWarning,
287        )
288        if doc is not None:
289            err_msg: str = f"cannot pass both `doc` and `description`: {doc=}, {kwargs['description']=}"
290            raise ValueError(err_msg)
291        doc = kwargs.pop("description")
292
293    return SerializableField(
294        default=default,
295        default_factory=default_factory,
296        init=init,
297        repr=repr,
298        hash=hash,
299        compare=compare,
300        metadata=metadata,
301        kw_only=kw_only,
302        serialize=serialize,
303        serialization_fn=serialization_fn,
304        deserialize_fn=deserialize_fn,
305        assert_type=assert_type,
306        custom_typecheck_fn=custom_typecheck_fn,
307        **kwargs,
308    )

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

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

copy all values from a dataclasses.Field to new SerializableField

name
type
default
default_factory
init
repr
hash
compare
metadata
kw_only
doc
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, doc: str | None = None, 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:
202def serializable_field(  # general implementation
203    *_args,
204    default: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
205    default_factory: Union[Any, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
206    init: bool = True,
207    repr: bool = True,
208    hash: Optional[bool] = None,
209    compare: bool = True,
210    doc: str | None = None,
211    metadata: Optional[types.MappingProxyType] = None,
212    kw_only: Union[bool, dataclasses._MISSING_TYPE] = dataclasses.MISSING,
213    serialize: bool = True,
214    serialization_fn: Optional[Callable[[Any], Any]] = None,
215    deserialize_fn: Optional[Callable[[Any], Any]] = None,
216    assert_type: bool = True,
217    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
218    **kwargs: Any,
219) -> Any:
220    """Create a new `SerializableField`
221
222    ```
223    default: Sfield_T | dataclasses._MISSING_TYPE = dataclasses.MISSING,
224    default_factory: Callable[[], Sfield_T]
225    | dataclasses._MISSING_TYPE = dataclasses.MISSING,
226    init: bool = True,
227    repr: bool = True,
228    hash: Optional[bool] = None,
229    compare: bool = True,
230    doc: str | None = None, # new in python 3.14. can alternately pass `description` to match pydantic, but this is discouraged
231    metadata: types.MappingProxyType | None = None,
232    kw_only: bool | dataclasses._MISSING_TYPE = dataclasses.MISSING,
233    # ----------------------------------------------------------------------
234    # new in `SerializableField`, not in `dataclasses.Field`
235    serialize: bool = True,
236    serialization_fn: Optional[Callable[[Any], Any]] = None,
237    loading_fn: Optional[Callable[[Any], Any]] = None,
238    deserialize_fn: Optional[Callable[[Any], Any]] = None,
239    assert_type: bool = True,
240    custom_typecheck_fn: Optional[Callable[[type], bool]] = None,
241    ```
242
243    # new Parameters:
244    - `serialize`: whether to serialize this field when serializing the class'
245    - `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`
246    - `loading_fn`: function taking the serialized object and returning the instance of the field. If not provided, will take object as-is.
247    - `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.
248    - `assert_type`: whether to assert the type of the field when loading. if `False`, will not check the type of the field.
249    - `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.
250
251    # Gotchas:
252    - `loading_fn` takes the dict of the **class**, not the field. if you wanted a `loading_fn` that does nothing, you'd write:
253
254    ```python
255    class MyClass:
256        my_field: int = serializable_field(
257            serialization_fn=lambda x: str(x),
258            loading_fn=lambda x["my_field"]: int(x)
259        )
260    ```
261
262    using `deserialize_fn` instead:
263
264    ```python
265    class MyClass:
266        my_field: int = serializable_field(
267            serialization_fn=lambda x: str(x),
268            deserialize_fn=lambda x: int(x)
269        )
270    ```
271
272    In the above code, `my_field` is an int but will be serialized as a string.
273
274    note that if not using ZANJ, and you have a class inside a container, you MUST provide
275    `serialization_fn` and `loading_fn` to serialize and load the container.
276    ZANJ will automatically do this for you.
277
278    # 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
279    """
280    assert len(_args) == 0, f"unexpected positional arguments: {_args}"
281
282    if "description" in kwargs:
283        import warnings
284
285        warnings.warn(
286            "`description` is deprecated, use `doc` instead",
287            DeprecationWarning,
288        )
289        if doc is not None:
290            err_msg: str = f"cannot pass both `doc` and `description`: {doc=}, {kwargs['description']=}"
291            raise ValueError(err_msg)
292        doc = kwargs.pop("description")
293
294    return SerializableField(
295        default=default,
296        default_factory=default_factory,
297        init=init,
298        repr=repr,
299        hash=hash,
300        compare=compare,
301        metadata=metadata,
302        kw_only=kw_only,
303        serialize=serialize,
304        serialization_fn=serialization_fn,
305        deserialize_fn=deserialize_fn,
306        assert_type=assert_type,
307        custom_typecheck_fn=custom_typecheck_fn,
308        **kwargs,
309    )

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,
doc: str | None = None, # new in python 3.14. can alternately pass `description` to match pydantic, but this is discouraged
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