You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Callbacks to Encoder/Decoder are not respected in datetime objects #669
Both dec_hook and enc_hook arguments are not respected in all encoders and decoders (tested on JSON and YAML) when datetime objects are used. Note that the print functions in both hooks are not run, and the variable buf contains an ISO 8601 duration string instead of a number (as seen from enc_hook).
Attached is a sample script to show that custom decoding of datetime.timedelta objects is not supported. It also doesn't work for datetime.datetime objects.
importmsgspecfromtypingimportAny, Typefromdatetimeimporttimedeltadefenc_hook(obj: Any) ->Any:
print("Encoding")
ifisinstance(obj, timedelta):
# convert the timedelta to a numberreturnobj.total_seconds()
else:
# Raise a NotImplementedError for other typesraiseNotImplementedError(f"Objects of type {type(obj)} are not supported")
defdec_hook(type: Type, obj: Any) ->Any:
print("Decoding", type)
# `type` here is the value of the custom type annotation being decoded.iftypeistimedelta:
# Convert ``obj`` (which should be a ``number``) to a timedeltareturntimedelta(seconds=obj)
else:
# Raise a NotImplementedError for other typesraiseNotImplementedError(f"Objects of type {type} are not supported")
classMyMessage(msgspec.Struct):
field_1: strfield_2: timedeltaenc=msgspec.json.Encoder(enc_hook=enc_hook)
dec=msgspec.json.Decoder(MyMessage, dec_hook=dec_hook)
msg=MyMessage("some string", timedelta(seconds=5))
# Doesn't work for JSON decoderbuf=enc.encode(msg)
print(buf)
a=dec.decode(buf)
print(a)
# Doesn't work for YAML decoders eitherbuf=msgspec.yaml.encode(msg, enc_hook=enc_hook)
print(buf)
a=msgspec.yaml.decode(buf, type=MyMessage, dec_hook=dec_hook)
print(a)
.encode and .decode methods under the hood call msgspec.to_builtins and msgspec.convert functions respectively.
Both functions have parameter builtin_types, which disables processing of specified builtin types by the msgspec, but it does not pass those types to *_hook methods, only non-builtin types are passed to *_hooks.
Wether this is a bug or by design - only @jcrist can tell (no pun intended :-)
But it definitely feels like a bug.
The above can be illustrated with:
importmsgspecasmsimportdatetimeasdtdefenc_hook(obj: Any) ->Any:
print("Encoding")
ifisinstance(obj, T):
returnobj.nameifisinstance(obj, dt.timedelta):
# convert the timedelta to a numberreturnobj.total_seconds()
else:
# Raise a NotImplementedError for other typesraiseNotImplementedError(f"Objects of type {type(obj)} are not supported")
classT:
def__init__(self, name='some name'):
self.name=nameclassMyMessage(ms.Struct):
field_1: Tfield_2: dt.timedeltamsg=MyMessage(T(), dt.timedelta(seconds=5))
msg_encoded=ms.to_builtins(
msg,
builtin_types=(
dt.timedelta,
),
enc_hook=enc_hook
)
print(msg_encoded)
Activity
TheMythologist commentedon Apr 19, 2024
Update: This was broken sometime between version 0.16.0 and version 0.17.0.
Update: It was this specific commit that broke the hook for
datetime.timedeltaobjects: 2b72ebbUpdate: Seems like hooks for
datetime.datetimeobjects were broken since the startwikiped commentedon Apr 24, 2024
.encodeand.decodemethods under the hood callmsgspec.to_builtinsandmsgspec.convertfunctions respectively.Both functions have parameter
builtin_types, which disables processing of specified builtin types by themsgspec, but it does not pass those types to*_hookmethods, only non-builtin types are passed to*_hooks.Wether this is a bug or by design - only @jcrist can tell (no pun intended :-)
But it definitely feels like a bug.
The above can be illustrated with:
The above outputs:
I can see 2 ways to overcome this behaviour until (if ever) it gets changed:
encode/decodemethod where you can control what happens to dict produced bymsgspecbefore it gets sent toen/de-coders._hooks.