Repository navigation
Closed
Description
- cattrs version: All versions, as far as I can tell
- Python version: Python 3.7
- Operating System: Arch Linux
Description
cattr.structure breaks when PEP 563 is activated. This can be done in Python 3.7 with the new from __future__ import annotations.
In short, this defers the evaluation of type annotations. So consider this class:
@attr.s(auto_attribs=True)
class Foo:
bar: strTraditionally, __annotations__ looks like this:
>>> Foo.__annotations__
... {'bar': str}
but under PEP 563 (and eventually the default behaviour), it'll look like this:
>>> Foo.__annotations__
... {'bar': 'str'}
This seems to confuse cattr and causes problems. Round-tripping the above class, we now see exceptions like this:
In [16]: cattr.structure(cattr.unstructure(Foo('Hello World')), Foo)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-16-1bbcc538d3df> in <module>()
----> 1 cattr.structure(cattr.unstructure(Foo('Hello World')), Foo)
~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in structure(self, obj, cl)
176 """Convert unstructured Python data structures to structured data."""
177 # type: (Any, Type) -> Any
--> 178 return self._structure_func.dispatch(cl)(obj, cl)
179
180 # Classes to Python primitives.
~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in structure_attrs_fromdict(self, obj, cl)
294 except KeyError:
295 continue
--> 296 conv_obj[name] = dispatch(type_)(val, type_)
297
298 return cl(**conv_obj)
~/.local/share/virtualenvs/aastrology-5jK3bc4O/lib/python3.7/site-packages/cattr/converters.py in _structure_default(self, obj, cl)
238 "it.".format(cl)
239 )
--> 240 raise ValueError(msg)
241
242 def _structure_call(self, obj, cl):
ValueError: Unsupported type: str. Register a structure hook for it.
This fix is probably to apply this logic: https://www.python.org/dev/peps/pep-0563/#resolving-type-hints-at-runtime
Here:
Which I'm currently digging into.
Reacted by José Fernández Ramos, Samuel Bishop, Fabian Raab, Oliver Berger, satwell and Giorgio Balestrieri
Reacted by John Walker and Oliver Berger
Metadata
Metadata
Assignees
Labels
No labels
Activity
vodik commentedon Sep 14, 2018
I have a proof of concept fix, but won't be able to polish it up until tonight.
This fix will hit two birds with one stone, as I guess it also means forward declarations where broken as well, as per #13
Add an evaluation context to support typing.ForwardDef
Add an evaluation context to support typing.ForwardDef
Add an evaluation context to support typing.ForwardDef
ceelian commentedon Feb 7, 2019
Today I run in the same bug, took me quite some time to figure out that the error
ValueError: Unsupported type: List[str]. Register a structure hook for itwas caused by
from __future__ import annotationsAny plans to release a new version with the suggested fix?
BrendanSimon commentedon Oct 10, 2019
Oct 2019 and this bug still seems to be present. The proof of concept mentioned above is dated Sep 2018.
Is there any fix pending, or some workaround? The only thing I can think of is remove the
from __future__ import annotations, but that defeats the purpose of it, and as mentioned will be the default in Python 4 (a long ways off I know, but ...)ex-nerd commentedon Jan 21, 2020
Still happening in 2020. Has anyone figured out a workaround?
johnwlockwood commentedon Feb 14, 2020
This is a disappointing issue. It's my first day using cattrs and I hit this.
johnwlockwood commentedon Feb 14, 2020
This script demonstrates it.
Fixes python-attrs#41: Breaks when PEP 563 is active.
Issue python-attrs#41: Reimplemented string type conversion using mor…
Fixes python-attrs#41: Breaks when PEP 563 is active.
Issue python-attrs#41: Reimplemented string type conversion using mor…
jacobg commentedon Aug 19, 2020
Any update on resolving this issue?
BrendanSimon commentedon Aug 19, 2020
I work around this by explicitly setting the
typeargument in theattr.ib()statement.e.g.
foo = attr.ib( type=int )I'm not sure how well that works with MyPy (if at all), but I also add typing comments.
4 remaining items
tmr232 commentedon Jan 5, 2021
GenConvertersolved it for meCould it be made the default converter, when calling
cattr.structure?FakeEmperor commentedon Feb 16, 2021
Hi all!
GenConverterdidn't work for me whenattrclass was also a generic:Raises this because
mappingisNoneI've added an
if mapping is not None:beforefor p in getattr(cl, "__parameters__", ()):to work around that, but I'm curious if this is a correct fix, because it seems that it won't work if I would have a field of a generic type.theunkn0wn1 commentedon Feb 16, 2021
You Specified the wrong type in the structure call. I believe cattrs needs to know what all the types are ahead-of-time, in your code you leave the typevar T undefined.
try
Tinche commentedon Feb 16, 2021
@theunkn0wn1 is correct. You could also use
A[Any]if you just want to pass through the value ofsomething.Tinche commentedon Feb 16, 2021
@tmr232 That's the plan, when we flesh it out a little more.
petergaultney commentedon Feb 17, 2021
@Tinche separately, i've been wondering - is there a reason
can't be backported to the original Converter's
structure_attrs_from_dict(and the similar code for unstructuring)?Tinche commentedon Feb 17, 2021
@petergaultney Due to the differences in how the converters work, this check would have to be done on every structuring call, not once, which I feel is wasteful. Things like these were one of the motivations for the GenConverter in the first place.
petergaultney commentedon Feb 17, 2021
I agree that sounds expensive. But wouldn't it be possible to have the original converter maintain a separate cache of resolved attr types, only resolving when the cache is empty?
Tinche commentedon Feb 17, 2021
@petergaultney Yep, I just didn't consider complicating it further since the GenConverter handles it out of the box. And I do plan to switching to GenConverter when we iron it out a little more anyway.
petergaultney commentedon Feb 17, 2021
if i made a PR would you accept it? :)
we make really heavy use of cattrs via
typecats, and we're not quite ready to move to GenConverter (though I'm very excited about it), but if the OG Converter supported new-style annotations we'd be able to upgrade to Python 3.9 immediately and stop the endless stream of:D
petergaultney commentedon Feb 17, 2021
(but I totally understand if it's not a maintenance penalty you're interested in)
Tinche commentedon Feb 17, 2021
@petergaultney If it's very important to you I'd consider it. Why can't you move to the GenConverter though? I can help with any issues.
petergaultney commentedon Feb 17, 2021
well,
typecatsis a very opinionated wrapper around attrs+cattrs, and i'm only partway through making it work with GenConverter.I've taken another look, and here's a wall of text describing some stuff which you will most likely want to skip:
typecatshas a need to 'decorate' the behavior of attrs type (un)structuring at runtime based on traits that apply in a general way (as opposed to being registered on a per-type basis). In other words, I want to be able to run code before and after every attrs-related strategy. [1]With the previous converter, the easiest way I found to do this was to wrap/decorate the attrs-specific structure/unstructure function in MultiStrategyDispatch. It would then dynamically have a chance to run code both before and after the structure/unstructure, but it still got to use the built-in cattrs dispatching mechanism. And this patch really only had to happen once, because we didn't bother registering a bunch of type-specific hooks directly with the converter.
With GenConverter, now there are two or three different paths to structure/unstructure an attrs type - once on initial structure using function dispatch, then possibly with singledispatch, then lastly with direct_dispatch. Instead of decorating 1 place dynamically at patch time, I end up needing to intercept both the dynamic dispatch and the register_cls_list call from GenConverter (so that i can decorate the registered generated code on initial use). I think I've got it mostly working but the code has become exceptionally hard to follow.
It's a big ball of ridiculousness, but it works great for our use cases with DynamoDB, allowing some very specific but dynamic (i.e. non-registered) behavior about the treatment of Literals, attrs classes which inherit from dict, and a few other things.
Resume reading here:
The next simplest approach, and one requiring no work on
cattrsitself, would be to build this directly into my own replacement for MultiStrategyDispatch that intercepts the dispatch but does an "is_attrs_cls" check on every structure type or unstructure object, whereTruewould cause the chosen function to be decorated at runtime on every dispatch. Not very efficient, particularly when we get down to scalar types, sincecattrsis already essentially doing this check. And I suppose if I use my ownlru_cachethe cost probably wouldn't be as bad as I think. Anyway, our primary concerns are developer productivity, not efficiency, so it's almost certainly worth it for us.All this to say... I think I'm asking for a very narrow patch to the old Converter when what I really want is something more general purpose, and either way I should probably do a bit more work on my end to see if I can reasonably support my own goals using GenConverter as-is.
A final note: thanks for this library. It's undoubtedly saved us hundreds of hours worth of developer time. I consider myself one of its biggest fans. :)
Tinche commentedon Feb 17, 2021
@petergaultney Thanks for the kind words. Can you open a different ticket for this; I think what you need is actually possible with the GenConverter without much difficulty.