THRIFT-5715 Python Exceptions mutable with slots

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](https://github.com/apache/thrift/pull/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.
diff --git a/test/py/TestClient.py b/test/py/TestClient.py
index 8a30c3a..61a9c60 100755
--- a/test/py/TestClient.py
+++ b/test/py/TestClient.py
@@ -256,6 +256,35 @@
         y = self.client.testMultiException('success', 'foobar')
         self.assertEqual(y.string_thing, 'foobar')
 
+    def testException__traceback__(self):
+        print('testException__traceback__')
+        self.client.testException('Safe')
+        expect_slots = uses_slots = False
+        expect_dynamic = uses_dynamic = False
+        try:
+            self.client.testException('Xception')
+            self.fail("should have gotten exception")
+        except Xception as x:
+            uses_slots = hasattr(x, '__slots__')
+            uses_dynamic = (not isinstance(x, TException))
+            # We set expected values here so that we get clean tracebackes when
+            # the assertions fail.
+            try:
+                x.__traceback__ = x.__traceback__
+                # If `__traceback__` was set without errors than we expect that
+                # the slots option was used and that dynamic classes were not.
+                expect_slots = True
+                expect_dynamic = False
+            except Exception as e:
+                self.assertTrue(isinstance(e, TypeError))
+                # There are no other meaningful tests we can preform because we
+                # are unable to determine the desired state of either `__slots__`
+                # or `dynamic`.
+                return
+
+        self.assertEqual(expect_slots, uses_slots)
+        self.assertEqual(expect_dynamic, uses_dynamic)
+
     def testOneway(self):
         print('testOneway')
         start = time.time()