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
An issue I'm currently facing is that, since msgspec supports attrs classes natively, there is no way to get those validators to work; Msgspec doesn't call them and I also can't make sure they're called via a dec_hook.
Yes, attrs validators aren't currently called. I think supporting them might make sense, but I'm a bit torn. Many of the builtin attrs validators are already supported in msgspec via other mechanisms, and will be both slower and duplicate work. For example:
The instance_of validator is unnecessary, since msgspec will always check that it's an int. And the bounds-checks are better represented using a Meta annotation, where msgspec can validate them much faster than attrs can.
All that said - we can support attrs validators (no promises I'll get to it quickly). I just worry that supporting them will lead to inefficient usage that could be avoided by users making better use of msgspec native features.
A way to get this to work would be quite nice (=
If you absolutely need to get this to work now, you can do this by calling attrs.validate on self in a __attrs_post_init__ call. This is bad since it will run the validators twice when constructed outside of msgspec (once during normal field init, then again during post-init), but since msgspec does call __attrs_post_init__ this will force msgspec to call them.
In [34]: @define
...: classEx:
...: x: int=field(validator=[instance_of(int), lt(100), gt(0)])
...: def__attrs_post_init__(self):
...: attrs.validate(self)
...:
In [35]: x=Ex(1) # validators are called *twice* hereIn [36]: msgspec.json.decode(b'{"x": 1}', type=Ex) # validators are called *once* hereOut[36]: Ex(x=1)
In [37]: msgspec.json.decode(b'{"x": -1}', type=Ex) # validators are called *once* here---------------------------------------------------------------------------ValueErrorTraceback (mostrecentcalllast)
CellIn[34], line5, inEx.__attrs_post_init__(self)
4def__attrs_post_init__(self):
---->5attrs.validate(self)
File~/miniconda3/envs/msgspec/lib/python3.9/site-packages/attr/_make.py:1964, invalidate(inst)
1963ifvisnotNone:
->1964v(inst, a, getattr(inst, a.name))
File~/miniconda3/envs/msgspec/lib/python3.9/site-packages/attr/_make.py:2905, in_AndValidator.__call__(self, inst, attr, value)
2904forvinself._validators:
->2905v(inst, attr, value)
File~/miniconda3/envs/msgspec/lib/python3.9/site-packages/attr/validators.py:474, in_NumberValidator.__call__(self, inst, attr, value)
473ifnotself.compare_func(value, self.bound):
-->474raiseValueError(
475"'{name}' must be {op} {bound}: {value}".format(
476name=attr.name,
477op=self.compare_op,
478bound=self.bound,
479value=value,
480 )
481 )
ValueError: 'x'mustbe>0: -1Theaboveexceptionwasthedirectcauseofthefollowingexception:
ValidationErrorTraceback (mostrecentcalllast)
CellIn[37], line1---->1msgspec.json.decode(b'{"x": -1}', type=Ex)
ValidationError: 'x'mustbe>0: -1
Many of the builtin attrs validators are already supported in msgspec via other mechanisms, and will be both slower and duplicate work
No argument there. This definitely makes sense that if you're already using msgspec. However, my use case is - as usual - Litestar, where we swapped to msgspec for our internal parsing and validation needs. This includes handling of attrs classes a user has passed in.
The concrete issue there is that we currently have no way to validate those correctly, whereas prior to msgspec adding support for attrs we could via an enc_hook.
So essentially the issue here for us is that msgspec only has partial support for decoding/converting into attrs, but no way to override its attrs handling.
I'm not sure how far along you are in the decision making process of finding a "better way" to handle the hooks (i.e. the one that would allow to override natively supported types), but I feel like that would quite often be a solution to the issues that arise due to different use cases.
We do need a way to add custom validator to Struct class.
I'm building a 3rd-party SDK for a platform, in my case, we need to validate if the provided url is valid or not, by sending an API call to an endpoint.
classUser(msgspec.Struct):
url: str# How can I do custom validate?
We do need a way to add custom validator to Struct class.
This is unrelated to this issue. #483 is the issue tracking that feature.
in my case, we need to validate if the provided url is valid or not, by sending an API call to an endpoint.
Are you saying you make an API call during validation? If so, I wouldn't handle that there (even when using pydantic) - making an API call everytime a User instance is created seems wasteful, and having side effects like an API call hidden inside a constructor may make it easy to miss that the API call is happening. It also won't compose well if working with async pythong.
Instead, I'd handle this separately outside of the JSON decoding, so it's explicit when the API request is being made. Something like:
user=msgspec.json.decode(msg, type=User)
user.validate_url() # explicit when the URL validating request happensawaituser.validate_url() # could even make it async if needed
Activity
jcrist commentedon Aug 24, 2023
Yes, attrs validators aren't currently called. I think supporting them might make sense, but I'm a bit torn. Many of the builtin attrs validators are already supported in msgspec via other mechanisms, and will be both slower and duplicate work. For example:
would be better written in msgspec as
The
instance_ofvalidator is unnecessary, since msgspec will always check that it's anint. And the bounds-checks are better represented using aMetaannotation, where msgspec can validate them much faster than attrs can.All that said - we can support attrs validators (no promises I'll get to it quickly). I just worry that supporting them will lead to inefficient usage that could be avoided by users making better use of msgspec native features.
If you absolutely need to get this to work now, you can do this by calling
attrs.validateonselfin a__attrs_post_init__call. This is bad since it will run the validators twice when constructed outside of msgspec (once during normal field init, then again during post-init), but since msgspec does call__attrs_post_init__this will force msgspec to call them.provinzkraut commentedon Aug 24, 2023
No argument there. This definitely makes sense that if you're already using msgspec. However, my use case is - as usual - Litestar, where we swapped to msgspec for our internal parsing and validation needs. This includes handling of attrs classes a user has passed in.
The concrete issue there is that we currently have no way to validate those correctly, whereas prior to msgspec adding support for attrs we could via an
enc_hook.So essentially the issue here for us is that msgspec only has partial support for decoding/converting into attrs, but no way to override its attrs handling.
I'm not sure how far along you are in the decision making process of finding a "better way" to handle the hooks (i.e. the one that would allow to override natively supported types), but I feel like that would quite often be a solution to the issues that arise due to different use cases.
FHU-yezi commentedon Aug 25, 2023
We do need a way to add custom validator to Struct class.
I'm building a 3rd-party SDK for a platform, in my case, we need to validate if the provided
urlis valid or not, by sending an API call to an endpoint.In Pydantic, I can do this:
jcrist commentedon Aug 25, 2023
This is unrelated to this issue. #483 is the issue tracking that feature.
Are you saying you make an API call during validation? If so, I wouldn't handle that there (even when using pydantic) - making an API call everytime a
Userinstance is created seems wasteful, and having side effects like an API call hidden inside a constructor may make it easy to miss that the API call is happening. It also won't compose well if working with async pythong.Instead, I'd handle this separately outside of the JSON decoding, so it's explicit when the API request is being made. Something like: