Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

msgspec.inspect error #807

Open
lblanquet opened this issue Jan 24, 2025 · 2 comments
Open

msgspec.inspect error #807

lblanquet opened this issue Jan 24, 2025 · 2 comments

Comments

@lblanquet
Copy link

lblanquet commented Jan 24, 2025

Description

I want to define JSON fields.
I thus use the following code.

import msgspec
import uuid
from typing import TypeAlias

t_JSON: TypeAlias = dict[str, "t_JSON"] | list["t_JSON"] | str | int | float | bool | None

class t_1(msgspec.Struct):
        n: int
        uid: uuid.UUID
        jdata: t_JSON

print('Version:', msgspec.__version__)
msgspec.inspect.type_info(t_1)

and it issues an exception (I don't know if it's actually a bug):

Version: 0.19.0
Traceback (most recent call last):
  File "c:\program files\wing pro 10\bin\dbg\src\debug\tserver\dbgutils.py", line 2334, in to_trace
  Python Shell, prompt 6, line 1
    msgspec.inspect.type_info(t_1)
  File "d:\python\python312\lib\site-packages\msgspec\inspect.py", line 629, in type_info
    return multi_type_info([type])[0]
  File "d:\python\python312\lib\site-packages\msgspec\inspect.py", line 598, in multi_type_info
    return _Translator(types).run()
  File "d:\python\python312\lib\site-packages\msgspec\inspect.py", line 744, in run
    MsgpackDecoder(Tuple[self.types])
builtins.TypeError: Type 'ForwardRef('t_JSON')' is not supported```

@rafalkrupinski
Copy link

Did you try adapting JsonValue from pydantic? Your approach fails there too, but they've found a work-around.

@lblanquet
Copy link
Author

lblanquet commented Jan 24, 2025

Thank you for the hint.
I've cloned pydantic. It's not easy to understand if their approach is reproductible with msgspec .
I've found the following workaround that works (It's a little bit treaky and maybe fragile 😅) :

import msgspec
import orjson
from typing import TypeAlias, Any
from icecream import ic

t_JSON: TypeAlias = dict[str, "t_JSON"] | list["t_JSON"] | str | int | float | bool | None

class JSON:
    def __init__(self, v : t_JSON):
        self.v = v
    
    def __repr__(self):
        return self.v.__repr__()

class T2(msgspec.Struct):
    n: int    
    jdata: JSON
    
    @property    
    def jdata(self):
        return self.jdata.v
    
    @jdata.setter    
    def jdata(self, v):
        self.jdata = JSON(v)
    
    def __post_init__(self):
        if not isinstance(self.jdata, JSON):
            self.jdata = JSON(self.jdata)

jdata=[1,2,3,{"n":1, "l":[1,2,3]}]

t2 = T2(n=1, jdata=jdata)

def m_enc_hook(obj: Any) -> Any:
    if isinstance(obj, JSON):
        result = orjson.dumps(obj.v)
        return result     
    else:
        # Raise a NotImplementedError for other types
        raise NotImplementedError(f"Objects of type {type(obj)} are not supported")

def j_enc_hook(obj: Any) -> Any:
    if isinstance(obj, JSON):
        result = orjson.dumps(obj.v).decode('utf8')
        return result     
    else:
        # Raise a NotImplementedError for other types
        raise NotImplementedError(f"Objects of type {type(obj)} are not supported")

m_enc = msgspec.msgpack.Encoder(enc_hook=m_enc_hook)
j_enc = msgspec.json.Encoder(enc_hook=j_enc_hook)


print('t2.jdata', t2.jdata, 'expected:', jdata)
ic(m_enc.encode(t2))
ic(j_enc.encode(t2))
ic(msgspec.inspect.type_info(T2))

result is as expected:

t2.jdata [1, 2, 3, {'n': 1, 'l': [1, 2, 3]}] expected: [1, 2, 3, {'n': 1, 'l': [1, 2, 3]}]
ic| m_enc.encode(t2): b'\x82\xa1n\x01\xa5jdata\xc4\x1b[1,2,3,{"n":1,"l":[1,2,3]}]'
ic| j_enc.encode(t2): b'{"n":1,"jdata":"[1,2,3,{\\"n\\":1,\\"l\\":[1,2,3]}]"}'
ic| msgspec.inspect.type_info(T2): StructType(cls=<class '__main__.T2'>, fields=(Field(name='n', encode_name='n', type=IntType(gt=None, ge=None, lt=None, le=None, multiple_of=None), required=True, default=NODEFAULT, default_factory=NODEFAULT), Field(name='jdata', encode_name='jdata', type=CustomType(cls=<class '__main__.JSON'>), required=False, default=<property object at 0x000001F6043B2BB0>, default_factory=NODEFAULT)), tag_field=None, tag=None, array_like=False, forbid_unknown_fields=False)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants