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
THRIFT-5715: Python non-user defined fields mutable with slots #2816
Conversation
3d1ba71
to
6bd6ace
Compare
appvayor is currently failing, but it's also failing on all other PRs. Is this expected? |
@@ -105,7 +106,9 @@ class t_py_generator : public t_generator { | |||
} | |||
if( import_dynbase_.empty()) { | |||
import_dynbase_ = "from thrift.protocol.TBase import TBase, TFrozenBase, TExceptionBase, TFrozenExceptionBase, TTransport\n"; | |||
} | |||
} | |||
} else if( iter->first.compare("immutable_exc") == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be called mutable_exc
or allow_mutable_exc
instead? otherwise you are making gen_immutable_exc_
to false when user pass in immutable_exc
arg to the compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea this naming is a bit confusing, I'll update it :)
kind of. but I think a better fix is to limit the scope of immutability of exceptions. we only need to limit the fields used by |
402f505
to
ec45983
Compare
I updated this so that the generated python only considers fields in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KTAtkinson can you please also paste what the new generated python exception code look like (with and without slots)?
test/py/TestClient.py
Outdated
@@ -256,6 +257,19 @@ def testMultiException(self): | |||
y = self.client.testMultiException('success', 'foobar') | |||
self.assertEqual(y.string_thing, 'foobar') | |||
|
|||
def testExceptionInContextManager(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really run python 3.11 in github actions so this test doesn't really verify that it fixes the issue you are seeing right now, but it will eventually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be some sort of test here. I originally wrote the test testing the functionality directly, assigning and deleting particular attributes. I wasn't able to write a test to ensure that user defined attributes are not editable as in some cases they are editable.
My thinking with this test case is that it will at least catch the specific failure when Thrift is updated to use Python 3.11. I did try to run this test with 3.11 but there are parts of the test script that aren't compatible with 3.11.
Thoughts on how to effectively write at test here?
if (gen_slots_) { | ||
out << indent() << "if attr not in self.__slots__:" << endl; | ||
} else { | ||
out << indent() << "if not self.__dict__.has_key(attr):" << endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might not work as expected:
$ python3
Python 3.11.4 (main, Jun 7 2023, 10:13:09) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
... _bar = None
...
>>> foo = Foo()
>>> foo.__dict__
{}
>>> foo._bar = 1
>>> foo.__dict__
{'_bar': 1}
it seems that the __dict__
would only have the key if it's used at least once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, it looks like each use defined field is explicitly set in the __init__
. I believe this would avoid this issue. Is that not correct?
Alternately I could keep track of user defined fields similar to how we do for __slots__
, thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my main concern here is that after contextmanager changes the __traceback__
it will start to appear in __dict__
and then it can no longer be changed again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long story short, __traceback__
does not appear in __dict__
from what I could track down it's because the implementation of Exception
is in C and __traceback__
is not technically an official property.
Fishy and I discussed off PR and decided that it was acceptable to only support context managers (in Python 3.11) with when using slots. This means any user that runs into this issue should use slots to get around their issue.
6d12bc0
to
409b4f9
Compare
@fishy Have time to take a look at this again? |
<< endl; | ||
out << indent() << "def __setattr__(self, *args):" << endl; | ||
indent_up(); | ||
if (gen_slots_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add some comments inside this if
block, with link to the jira ticket, to explain why we have this check.
out << endl; | ||
out << indent() << "def __delattr__(self, *args):" << endl; | ||
indent_up(); | ||
if (gen_slots_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
test/py/TestClient.py
Outdated
) | ||
except Exception as e: | ||
self.assertTrue( | ||
isinstance(e, TypeError), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be isinstance(e, TypeError) and not uses_slots
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or probably better split into 2 separated asserts.
also to clarify, I said it's acceptable to limit the scope of this PR to only fix the slots use-case, but that does not fully fix the issue (so we cannot close the issue with this PR merged) and we should still fix the non-slots use-case later. |
please also rebase your change on top of latest master. there are 2 commits related to py compiler merged recently and I want to make sure that this does not break those changes. |
Please also change the title of this PR accordingly. You are no longer adding a flag to allow mutable exceptions. |
409b4f9
to
906ff2a
Compare
// Not user-provided fields should be editable so that the Python Standard L`ibrary can edit | ||
// internal fields of std library base classes. For example, in Python 3.11 context managers | ||
// edit the `__traceback__` field on Exceptions. Allowing this to work with `__slots__` is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
In Python 3.11 exceptions generated by the compiler can't be used with a context manager because they are immutable. As of Python 3.11 `contextlib.contextmanager` sets `exc.__traceback__` in the event that the code in the context manager errors. As of Thrift v0.18.1 exceptions are generated as immutable by default. See [PR#1835](apache#1835) for more information about why exceptions were made immutable by default. This change makes all non-Thrift fields mutable when slots is used without dynamic. This will allow exceptions to be re-raised properly by the contextmanager in Python 3.11.
23e94f8
to
d3d8fd7
Compare
In Python 3.11 exceptions generated by the compiler can't be used with a
context manager because they are immutable. As of Python 3.11
contextlib.contextmanager
setsexc.__traceback__
in the event thatthe code in the context manager errors.
As of Thrift v0.18.1 exceptions are generated as immutable by default.
See PR#1835 for more information about why exceptions were made
immutable by default.
This change makes all non-Thrift fields mutable when slots is used without dynamic. This
will allow exceptions to be re-raised properly by the contextmanager in
Python 3.11.
With tutorial code generated from
head
this raises aTypeError
notan
InvalidOperation
:It raises this error:
The error after this change without slots, this error still happens:
After change with slots:
There is no diff in the generated code when slots is not used.
Here is the diff between
thrift
at head and this branch generating withslots
:[skip ci]
anywhere in the commit message to free up build resources.