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
{{ message }}
Repository navigation
assigning to __annotations__ has no effect in 3.14.0b1 under from __future__ import annotations #133778
from __future__ importannotationsimportannotationlibclassA:
a: intA.__annotations__= {"a": str}
print(A.__annotations__)
print(annotationlib.get_annotations(A))
this works in 3.14.0a7, has no effect in 3.14.0b1. there seems to be a new attribute __annotations_cache__ that the assignment goes into but it isn't returned from __annotations__:
changed the title [-]assigning to __annotations__ has no effect in 3.14.0b1[/-][+]assigning to __annotations__ has no effect in 3.14.0b1 under from __future__ import annotations[/+]on May 9, 2025
thanks. It's not that SQLAlchemy is doing everything great, or correctly, or what it said is allowed on the label, but at least it's a way to trap changes in behavior which you can then look at on a case by case basis to see if these changes were intended.
I'd like to ask for other opinions here (maybe @carljm@larryhastings) so I'm going to try to explain the situation.
Currently, classes with and without from __future__ import annotations store their annotations differently in the class namespace:
Without the future import, there is an __annotate_func__ key with the annotate function. If .__annotations__ is accessed, the annotations are cached in the __annotations_cache__ key.
With the future import, there is an __annotations__ key that holds the annotations.
When reading annotations (type_get_annotations), we first read the __annotations__ key in the class dictionary, then __annotations_cache__, then we get the annotate function.
When you set annotations on a type, type_set_annotations in typeobject.c is invoked. Currently, it always assigns to the __annotations_cache__ key. That leads to the bug described here where assigning to __annotations__ appears to do nothing for classes defined with the future import, because in the getter we always try __annotations__ before __annotations_cache__.
I'm considering a few options for how to solve this:
Make type_set_annotations always assign to the __annotations__ key. This has the tradeoff that you can now induce buggy behavior even without the future import: if you assign to .__annotations__ of a metaclass, then access .__annotations__ on an instance of that metaclass, you'll get the metaclass's annotations. I want that sort of misbehavior to be impossible when you don't use the future import, so in the long term we can say that accessing .__annotations__ on a class is safe.
Make type_set_annotations check if __annotations__ is already present in the class namespace. If so, replace it; otherwise replace __annotations_cache__. This avoids the bug above, but creates another subtle misbehavior. Consider a metaclass with the future enabled, and an instance of that metaclass defined without the future. Now if you assign to .__annotations__ of the class, on subsequent reads of .__annotations__ you'll get the metaclass's annotations, not the annotations you just set. However, you'll also get the metaclass's annotations before you do this; get_annotations() will correctly return the annotations you just set; and this bug only appears in the presence of the (now deprecated) future import.
We can also do something more invasive, like storing the annotations somewhere else on classes defined with the future. However, I'd like to avoid this, since I prefer to keep behavior mostly the same when the future import is enabled.
I ran the sqlalchemy test suite on a build of 3.14 with my three fixes #133795#133794#133772 applied.
On sqlalchemy main I get one failure (among >13k tests, impressive): FAILED test/typing/test_overloads.py::OverloadTest::test_typed_dicts[Union-expected1-_exclusions_01] - AssertionError: Unexpected success for 'test_typed_dicts' (Fails as of python 3.14.0b1), which I guess you marked as XFAIL, so it's good that it passes now. Same on the rel_2_0 branch.
I also tried to run the other tests in typing-extensions's third-party test suite. cattrs failed to install due to some issue I don't understand; a couple of others had failures that I fixed or opened issues about; but nothing else indicative of bugs in CPython.
that suite includes a new file test/orm/declarative/test_dc_transforms_future_anno_sync.py which is a copy of an existing suite that adds the from __future__ import annotations directive at the top. that's the suite that fails with the current issue. if you install SQLAlchemy dependencies as well as pytest you can run it directly as pytest test/orm/declarative/test_dc_transforms_future_anno_sync.py.
Thanks, that test suite also passes on my branch (though a bunch of things are skipped with "doesn't work for future annotations mode yet"; not sure if that's due to missing work on your side or due to CPython bugs).
that's great, those skipped tests are things on my end that I'd have to update to work with future (I'm comparing the contents of __annotations__ to known quantities, so some of these things become ForwardRefs etc), if the suite passes then we're all good. thanks very much!
Activity
zzzeek commentedon May 9, 2025
if this is a supported pattern (which I wasn't sure if it was or not) then I assume some tests will be added, thanks!
[-]assigning to __annotations__ has no effect in 3.14.0b1[/-][+]assigning to __annotations__ has no effect in 3.14.0b1 under from __future__ import annotations[/+]JelleZijlstra commentedon May 9, 2025
Yes it is, we are not testing the
from __future__ import annotationsversion well enough.zzzeek commentedon May 9, 2025
thanks! Here's hoping for a much more boring 3.14.0b2 release :)
JelleZijlstra commentedon May 9, 2025
Yes! I'm hoping to get all the fixes in over the weekend, after that I can try to run the SQLAlchemy test suite to make sure it's all fixed.
zzzeek commentedon May 9, 2025
thanks. It's not that SQLAlchemy is doing everything great, or correctly, or what it said is allowed on the label, but at least it's a way to trap changes in behavior which you can then look at on a case by case basis to see if these changes were intended.
JelleZijlstra commentedon May 10, 2025
I'd like to ask for other opinions here (maybe @carljm @larryhastings) so I'm going to try to explain the situation.
Currently, classes with and without
from __future__ import annotationsstore their annotations differently in the class namespace:__annotate_func__key with the annotate function. If.__annotations__is accessed, the annotations are cached in the__annotations_cache__key.__annotations__key that holds the annotations.When reading annotations (
type_get_annotations), we first read the__annotations__key in the class dictionary, then__annotations_cache__, then we get the annotate function.The background for this is described in https://peps.python.org/pep-0749/#annotations-and-metaclasses.
When you set annotations on a type,
type_set_annotationsin typeobject.c is invoked. Currently, it always assigns to the__annotations_cache__key. That leads to the bug described here where assigning to__annotations__appears to do nothing for classes defined with the future import, because in the getter we always try__annotations__before__annotations_cache__.I'm considering a few options for how to solve this:
type_set_annotationsalways assign to the__annotations__key. This has the tradeoff that you can now induce buggy behavior even without the future import: if you assign to.__annotations__of a metaclass, then access.__annotations__on an instance of that metaclass, you'll get the metaclass's annotations. I want that sort of misbehavior to be impossible when you don't use the future import, so in the long term we can say that accessing.__annotations__on a class is safe.type_set_annotationscheck if__annotations__is already present in the class namespace. If so, replace it; otherwise replace__annotations_cache__. This avoids the bug above, but creates another subtle misbehavior. Consider a metaclass with the future enabled, and an instance of that metaclass defined without the future. Now if you assign to.__annotations__of the class, on subsequent reads of.__annotations__you'll get the metaclass's annotations, not the annotations you just set. However, you'll also get the metaclass's annotations before you do this;get_annotations()will correctly return the annotations you just set; and this bug only appears in the presence of the (now deprecated) future import.So I'd like to go with option (2).
pythongh-133778: Fix setting `__annotations__` under PEP 563
JelleZijlstra commentedon May 11, 2025
I ran the sqlalchemy test suite on a build of 3.14 with my three fixes #133795 #133794 #133772 applied.
On sqlalchemy main I get one failure (among >13k tests, impressive):
FAILED test/typing/test_overloads.py::OverloadTest::test_typed_dicts[Union-expected1-_exclusions_01] - AssertionError: Unexpected success for 'test_typed_dicts' (Fails as of python 3.14.0b1), which I guess you marked as XFAIL, so it's good that it passes now. Same on the rel_2_0 branch.I also tried to run the other tests in typing-extensions's third-party test suite. cattrs failed to install due to some issue I don't understand; a couple of others had failures that I fixed or opened issues about; but nothing else indicative of bugs in CPython.
zzzeek commentedon May 12, 2025
that's great - the test suite that actually tests the assignment of
__annotations__issue however is not committed, it can be seen in this gerrit review: https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/5858you can checkout this gerrit review with commands such as:
that suite includes a new file
test/orm/declarative/test_dc_transforms_future_anno_sync.pywhich is a copy of an existing suite that adds thefrom __future__ import annotationsdirective at the top. that's the suite that fails with the current issue. if you install SQLAlchemy dependencies as well aspytestyou can run it directly aspytest test/orm/declarative/test_dc_transforms_future_anno_sync.py.JelleZijlstra commentedon May 12, 2025
Thanks, that test suite also passes on my branch (though a bunch of things are skipped with "doesn't work for future annotations mode yet"; not sure if that's due to missing work on your side or due to CPython bugs).
zzzeek commentedon May 12, 2025
that's great, those skipped tests are things on my end that I'd have to update to work with future (I'm comparing the contents of
__annotations__to known quantities, so some of these things become ForwardRefs etc), if the suite passes then we're all good. thanks very much!gh-133778: Fix setting `__annotations__` under PEP 563 (#133794)
pythongh-133778: Fix setting `__annotations__` under PEP 563 (pythonG…
[3.14] gh-133778: Fix setting `__annotations__` under PEP 563 (GH-133794
pythongh-133778: Fix setting `__annotations__` under PEP 563 (python#…
pythongh-133778: Fix setting `__annotations__` under PEP 563 (python#…
pythongh-133778: Fix setting `__annotations__` under PEP 563 (python#…