refactor: Non-working first-move of serialization logic

main
Sofus Albert Høgsbro Rose 2024-04-15 15:21:13 +02:00
parent e1f11f6d68
commit 3def85e24f
Signed by: so-rose
GPG Key ID: AD901CB0F3701434
3 changed files with 160 additions and 152 deletions

View File

@ -510,7 +510,7 @@ Unreported:
## Keyed Cache
- [ ] Implement `bl_cache.KeyedCache` for, especially, abstracting the caches underlying the input and output sockets.
- [x] Implement `bl_cache.KeyedCache` for, especially, abstracting the caches underlying the input and output sockets.
@ -546,7 +546,7 @@ We need support for arbitrary objects, but still backed by the persistance seman
- [ ] Similarly, a field method that gets the 'blfield__' prop data as a dictionary.
### Parallel Features
- [ ] Move serialization work to a `utils`.
- [x] Move serialization work to a `utils`.
- [ ] Also make ENCODER a function that can shortcut the easy cases.
- [ ] For serializeability, let the encoder/decoder be able to make use of an optional `.msgspec_encodable()` and similar decoder respectively, and add support for these in the ENCODER/DECODER functions.
- [ ] Define a superclass for `SocketDef` and make everyone inherit from it

View File

@ -5,14 +5,8 @@ import inspect
import typing as typ
import bpy
import msgspec
import sympy as sp
import sympy.physics.units as spu
from ...utils import extra_sympy_units as spux
from ...utils import logger
from . import contracts as ct
from . import managed_objs, sockets
from ...utils import logger, serialize
log = logger.get(__name__)
@ -39,149 +33,10 @@ class BLInstance(typ.Protocol):
) -> None: ...
EncodableValue: typ.TypeAlias = typ.Any ## msgspec-compatible
PropGetMethod: typ.TypeAlias = typ.Callable[[BLInstance], EncodableValue]
PropSetMethod: typ.TypeAlias = typ.Callable[[BLInstance, EncodableValue], None]
####################
# - (De)Serialization
####################
EncodedComplex: typ.TypeAlias = tuple[float, float] | list[float, float]
EncodedSympy: typ.TypeAlias = str
EncodedManagedObj: typ.TypeAlias = tuple[str, str] | list[str, str]
EncodedPydanticModel: typ.TypeAlias = tuple[str, str] | list[str, str]
def _enc_hook(obj: typ.Any) -> EncodableValue:
"""Translates types not natively supported by `msgspec`, to an encodable form supported by `msgspec`.
Parameters:
obj: The object of arbitrary type to transform into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if isinstance(obj, complex):
return (obj.real, obj.imag)
if isinstance(obj, sp.Basic | sp.MatrixBase | sp.Expr | spu.Quantity):
return sp.srepr(obj)
if isinstance(obj, managed_objs.ManagedObj):
return (obj.name, obj.__class__.__name__)
if isinstance(obj, ct.schemas.SocketDef):
return (obj.model_dump(), obj.__class__.__name__)
msg = f'Can\'t encode "{obj}" of type {type(obj)}'
raise NotImplementedError(msg)
def _dec_hook(_type: type, obj: EncodableValue) -> typ.Any:
"""Translates the `msgspec`-encoded form of an object back to its true form.
Parameters:
_type: The type to transform the `msgspec`-encoded object back into.
obj: The encoded object of to transform back into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if _type is complex and isinstance(obj, EncodedComplex):
return complex(obj[0], obj[1])
if (
_type is sp.Basic
and isinstance(obj, EncodedSympy)
or _type is sp.Expr
and isinstance(obj, EncodedSympy)
or _type is sp.MatrixBase
and isinstance(obj, EncodedSympy)
or _type is spu.Quantity
and isinstance(obj, EncodedSympy)
):
return sp.sympify(obj).subs(spux.ALL_UNIT_SYMBOLS)
if (
_type is managed_objs.ManagedBLMesh
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLImage
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLModifier
and isinstance(obj, EncodedManagedObj)
):
return {
'ManagedBLMesh': managed_objs.ManagedBLMesh,
'ManagedBLImage': managed_objs.ManagedBLImage,
'ManagedBLModifier': managed_objs.ManagedBLModifier,
}[obj[1]](obj[0])
if _type is ct.schemas.SocketDef:
return getattr(sockets, obj[1])(**obj[0])
msg = f'Can\'t decode "{obj}" to type {type(obj)}'
raise NotImplementedError(msg)
ENCODER = msgspec.json.Encoder(enc_hook=_enc_hook, order='deterministic')
_DECODERS: dict[type, msgspec.json.Decoder] = {
complex: msgspec.json.Decoder(type=complex, dec_hook=_dec_hook),
sp.Basic: msgspec.json.Decoder(type=sp.Basic, dec_hook=_dec_hook),
sp.Expr: msgspec.json.Decoder(type=sp.Expr, dec_hook=_dec_hook),
sp.MatrixBase: msgspec.json.Decoder(type=sp.MatrixBase, dec_hook=_dec_hook),
spu.Quantity: msgspec.json.Decoder(type=spu.Quantity, dec_hook=_dec_hook),
managed_objs.ManagedBLMesh: msgspec.json.Decoder(
type=managed_objs.ManagedBLMesh,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLImage: msgspec.json.Decoder(
type=managed_objs.ManagedBLImage,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLModifier: msgspec.json.Decoder(
type=managed_objs.ManagedBLModifier,
dec_hook=_dec_hook,
),
# managed_objs.ManagedObj: msgspec.json.Decoder(
# type=managed_objs.ManagedObj, dec_hook=_dec_hook
# ), ## Doesn't work b/c unions are not explicit
ct.schemas.SocketDef: msgspec.json.Decoder(
type=ct.schemas.SocketDef,
dec_hook=_dec_hook,
),
}
_DECODER_FALLBACK: msgspec.json.Decoder = msgspec.json.Decoder(dec_hook=_dec_hook)
@functools.cache
def DECODER(_type: type) -> msgspec.json.Decoder: # noqa: N802
"""Retrieve a suitable `msgspec.json.Decoder` by-type.
Parameters:
_type: The type to retrieve a decoder for.
Returns:
A suitable decoder.
"""
if (decoder := _DECODERS.get(_type)) is not None:
return decoder
return _DECODER_FALLBACK
def decode_any(_type: type, obj: str) -> typ.Any:
naive_decode = DECODER(_type).decode(obj)
if _type == dict[str, ct.schemas.SocketDef]:
return {
socket_name: getattr(sockets, socket_def_list[1])(**socket_def_list[0])
for socket_name, socket_def_list in naive_decode.items()
}
log.critical(
'Naive Decode of "%s" to "%s" (%s)', str(obj), str(naive_decode), str(_type)
)
return naive_decode
PropGetMethod: typ.TypeAlias = typ.Callable[[BLInstance], serialize.EncodableValue]
PropSetMethod: typ.TypeAlias = typ.Callable[
[BLInstance, serialize.EncodableValue], None
]
####################

View File

@ -0,0 +1,153 @@
import functools
import typing as typ
import msgspec
import sympy as sp
import sympy.physics.units as spu
from . import extra_sympy_units as spux
from . import logger
log = logger.get(__name__)
EncodableValue: typ.TypeAlias = typ.Any ## msgspec-compatible
####################
# - (De)Serialization
####################
EncodedComplex: typ.TypeAlias = tuple[float, float] | list[float, float]
EncodedSympy: typ.TypeAlias = str
EncodedManagedObj: typ.TypeAlias = tuple[str, str] | list[str, str]
EncodedPydanticModel: typ.TypeAlias = tuple[str, str] | list[str, str]
def _enc_hook(obj: typ.Any) -> EncodableValue:
"""Translates types not natively supported by `msgspec`, to an encodable form supported by `msgspec`.
Parameters:
obj: The object of arbitrary type to transform into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if isinstance(obj, complex):
return (obj.real, obj.imag)
if isinstance(obj, sp.Basic | sp.MatrixBase | sp.Expr | spu.Quantity):
return sp.srepr(obj)
if isinstance(obj, managed_objs.ManagedObj):
return (obj.name, obj.__class__.__name__)
if isinstance(obj, ct.schemas.SocketDef):
return (obj.model_dump(), obj.__class__.__name__)
msg = f'Can\'t encode "{obj}" of type {type(obj)}'
raise NotImplementedError(msg)
def _dec_hook(_type: type, obj: EncodableValue) -> typ.Any:
"""Translates the `msgspec`-encoded form of an object back to its true form.
Parameters:
_type: The type to transform the `msgspec`-encoded object back into.
obj: The encoded object of to transform back into an encodable value.
Returns:
A value encodable by `msgspec`.
Raises:
NotImplementedError: When the type transformation hasn't been implemented.
"""
if _type is complex and isinstance(obj, EncodedComplex):
return complex(obj[0], obj[1])
if (
_type is sp.Basic
and isinstance(obj, EncodedSympy)
or _type is sp.Expr
and isinstance(obj, EncodedSympy)
or _type is sp.MatrixBase
and isinstance(obj, EncodedSympy)
or _type is spu.Quantity
and isinstance(obj, EncodedSympy)
):
return sp.sympify(obj).subs(spux.ALL_UNIT_SYMBOLS)
if (
_type is managed_objs.ManagedBLMesh
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLImage
and isinstance(obj, EncodedManagedObj)
or _type is managed_objs.ManagedBLModifier
and isinstance(obj, EncodedManagedObj)
):
return {
'ManagedBLMesh': managed_objs.ManagedBLMesh,
'ManagedBLImage': managed_objs.ManagedBLImage,
'ManagedBLModifier': managed_objs.ManagedBLModifier,
}[obj[1]](obj[0])
if _type is ct.schemas.SocketDef:
return getattr(sockets, obj[1])(**obj[0])
msg = f'Can\'t decode "{obj}" to type {type(obj)}'
raise NotImplementedError(msg)
ENCODER = msgspec.json.Encoder(enc_hook=_enc_hook, order='deterministic')
_DECODERS: dict[type, msgspec.json.Decoder] = {
complex: msgspec.json.Decoder(type=complex, dec_hook=_dec_hook),
sp.Basic: msgspec.json.Decoder(type=sp.Basic, dec_hook=_dec_hook),
sp.Expr: msgspec.json.Decoder(type=sp.Expr, dec_hook=_dec_hook),
sp.MatrixBase: msgspec.json.Decoder(type=sp.MatrixBase, dec_hook=_dec_hook),
spu.Quantity: msgspec.json.Decoder(type=spu.Quantity, dec_hook=_dec_hook),
managed_objs.ManagedBLMesh: msgspec.json.Decoder(
type=managed_objs.ManagedBLMesh,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLImage: msgspec.json.Decoder(
type=managed_objs.ManagedBLImage,
dec_hook=_dec_hook,
),
managed_objs.ManagedBLModifier: msgspec.json.Decoder(
type=managed_objs.ManagedBLModifier,
dec_hook=_dec_hook,
),
# managed_objs.ManagedObj: msgspec.json.Decoder(
# type=managed_objs.ManagedObj, dec_hook=_dec_hook
# ), ## Doesn't work b/c unions are not explicit
ct.schemas.SocketDef: msgspec.json.Decoder(
type=ct.schemas.SocketDef,
dec_hook=_dec_hook,
),
}
_DECODER_FALLBACK: msgspec.json.Decoder = msgspec.json.Decoder(dec_hook=_dec_hook)
@functools.cache
def DECODER(_type: type) -> msgspec.json.Decoder: # noqa: N802
"""Retrieve a suitable `msgspec.json.Decoder` by-type.
Parameters:
_type: The type to retrieve a decoder for.
Returns:
A suitable decoder.
"""
if (decoder := _DECODERS.get(_type)) is not None:
return decoder
return _DECODER_FALLBACK
def decode_any(_type: type, obj: str) -> typ.Any:
naive_decode = DECODER(_type).decode(obj)
if _type == dict[str, ct.schemas.SocketDef]:
return {
socket_name: getattr(sockets, socket_def_list[1])(**socket_def_list[0])
for socket_name, socket_def_list in naive_decode.items()
}
log.critical(
'Naive Decode of "%s" to "%s" (%s)', str(obj), str(naive_decode), str(_type)
)
return naive_decode