-
-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Is there a way to add a "catch-all" fallback hook? #311
Comments
Hello, good question. To give a little context, cattrs uses two mechanisms to get your hook - the first one is a We don't have a very good way of handling this currently, but I think we should. Here's what you can do right now to solve your problem: c = Converter()
c._structure_func._function_dispatch._handler_pairs[-1] = (
lambda _: True,
lambda v, _: pickle.loads(v),
False,
) You basically overwrite the fallback handler with your own. It's kind of filthy though, as evidenced by all the underscores. I think I'll do a small refactor to make setting this on the I'm a little wary of adding arguments to the Converter since there are so many already, it's getting to be intimidating for users. |
I see, I was kind of going that way too and thinking about monkeypatching (yuk) I don't think the number of arguments for |
Hm, you could maybe subclass Converter and just override Let's keep this issue open to track it as we think about it. |
That was my initial plan, but the problem is |
Bleh, that's by accident probably. |
haha I see. If that's fixed, I could totally subclass |
I just applied a refactor to the Now you can do this: import pickle
from cattrs import Converter
from cattrs.dispatch import MultiStrategyDispatch
c = Converter()
dispatch = MultiStrategyDispatch(lambda v, _: pickle.loads(v))
c._structure_func.copy_to(dispatch)
c._structure_func = dispatch
class Test:
def __init__(self, a: int) -> None:
self.a = a
def __repr__(self) -> str:
return f"Test(a={self.a})"
print(c.structure([pickle.dumps(Test(2))], list[Test])) |
Thanks! I'll watch out for the next release! |
Hiya, So I am one of the maintainers of starlite, and I am integrating attrs/cattrs as an optional parsing/validation framework (users will be able to choose between it an pydantic). The issue I am now faced with is exactly tha ability to handle catch-all structuring / unstructuring hooks, to deal with generic union types and all sorts of other userland types. It would be great if there was some way to configure a catch-all hook on the converter itself. Not necessarily as an |
Ok, so after playing with this for a while, this is what I am now doing to be able to handle union properly: def _create_default_structuring_hooks(
converter: cattrs.Converter,
) -> tuple[Callable[[Any, type[Any]], Any], Callable[[Any], Any]]:
"""Create scoped default hooks for a given converter.
Notes:
- We are forced to use this pattern because some types cannot be hanlded by cattrs out of the box. For example,
union types, optionals, complex union types etc.
Args:
converter: A conveter instance
Returns:
A tuple of hook handlers
"""
def _default_unstructuring_hook(value: Any) -> Any:
return converter.unstructure(value)
def _default_structuring_hook(value: Any, annotation: Any) -> Any:
for arg in unwrap_union(annotation) or get_args(annotation):
try:
return converter.structure(arg, value)
except ValueError:
continue
return converter.structure(annotation, value)
return (
_default_unstructuring_hook,
_default_structuring_hook,
)
class Converter(cattrs.Converter):
def __init__(self) -> None:
super().__init__()
# this is a hack to create a catch-all hook, see: https://github.com/python-attrs/cattrs/issues/311
self._structure_func._function_dispatch._handler_pairs[-1] = (
*_create_default_structuring_hooks(self),
False,
)
_converter: Converter = Converter() I would suggest that the converter exposes simple setters for this- converter_instance.set_default_structuring_hook(...)
converter_instance.set_default_unstructuring_hook(...) Additionally, it would be very good to be able to cache these. I will try throwing in an lru_cache decorator on the top, but I am not sure this is wise in this context. |
Howdy, I've just merged #441 bringing support for fallback hook factories (and fallback hooks). The docs are available here: https://catt.rs/en/latest/converters.html#fallback-hook-factories I've included a simple example with pickle. Hope this meets your needs! |
Wow thank you! @Tinche |
Description
First of all, thanks for the awesome library!
My use-case is that I want to be able to serialize as much as I can by falling back to something like
pickle
for non-attrs classes. I understand that pickle can't serialize everything, but I'm just trying to cover as many classes as I can. Unstructuring is not a problem by using things likejsonpickle
or the default option oforjson
, but structuring is giving me a lot of problems. I've tried to usecattrs.register_structure_hook_func
, but I haven't found a way to make it work. I guess one solution would be to find a way to check if a type is supported (either implicitly or explicitly) by the current converter or not, but I haven't found a good way to do so.What I Did
Any help would be greatly appreciated!
The text was updated successfully, but these errors were encountered: