-
-
Notifications
You must be signed in to change notification settings - Fork 114
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
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: