Skip to content

Commit 1fd5947

Browse files
authored
Merge pull request #3 from Sigmmma/fix_edge_case_with_attributes_not_being_returned
Fixed edge case where tk attributes would sometimes not be returned
2 parents c2d1171 + c261cb2 commit 1fd5947

File tree

1 file changed

+33
-25
lines changed

1 file changed

+33
-25
lines changed

threadsafe_tkinter/__init__.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,25 @@
2828
# ##############
2929
__author__ = "Devin Bobadilla"
3030
# YYYY.MM.DD
31-
__date__ = "2025.01.18"
32-
__version__ = (1, 0, 5)
31+
__date__ = "2025.02.02"
32+
__version__ = (1, 0, 6)
33+
34+
import os
35+
ENV_DISABLED = os.environ.get("THREADSAFE_TKINTER_DISABLE")
3336

3437
try:
3538
from tkinter import *
3639
except ImportError:
3740
from Tkinter import *
41+
3842
from queue import Queue as _Queue, Empty as _Empty
3943
try:
4044
from threading import currentThread as _curr_thread, _DummyThread
4145
except ImportError:
4246
from threading import current_thread as _curr_thread, _DummyThread
4347

4448
from types import FunctionType
49+
from traceback import format_exception
4550

4651
TKHOOK_UNHOOKING = -1
4752
TKHOOK_UNHOOKED = 0
@@ -66,31 +71,35 @@ def create_queue(self, size=0): return _Queue(size)
6671

6772
def __getattr__(self, attr_name):
6873
if self.tk_widget is None or self.tk_widget._tk is None:
69-
raise AttributeError(
70-
"self.tk_widget is None. Not hooked into a Tk instance.")
74+
raise AttributeError("Not hooked into a Tk instance.")
7175

7276
tk_attr = getattr(self.tk_widget._tk, attr_name)
73-
return (lambda *a, _f=tk_attr, **kw:
74-
self.call_tk_attr_threadsafe(_f, *a, **kw))
77+
78+
return (lambda *a, _f=tk_attr, _s=self, **kw:
79+
_s.call_tk_attr_threadsafe(_f, *a, **kw))
7580

7681
def call_tk_attr_threadsafe(self, tk_attr, *a, **kw):
7782
thread = self.get_curr_thread()
83+
7884
if thread == self.tk_thread or isinstance(thread, _DummyThread):
7985
# it is either safe to call from the thread the tkinter widget
8086
# is running on, or a dummy thread is running which is also safe
8187
return tk_attr(*a, **kw)
8288

8389
# add a request to the requests queue to call this attribute
84-
response_queue = self.create_queue(1)
85-
result, raise_result = None, None
90+
resp_queue, raise_result = self.create_queue(1), False
91+
result = undefined = object()
8692

87-
self.request_queue.put((response_queue, tk_attr, a, kw))
88-
while result is None and self.tk_widget is not None:
93+
self.request_queue.put((resp_queue, tk_attr, a, kw))
94+
while result is undefined and self.tk_widget is not None:
8995
try:
90-
response = response_queue.get(True, 3)
96+
response = resp_queue.get(True, 3)
9197
result, raise_result = response
9298
except _Empty:
9399
pass
100+
except ValueError as e:
101+
if result is undefined:
102+
result, raise_result = e, True
94103

95104
if raise_result:
96105
raise result
@@ -129,26 +138,26 @@ def process_requests(self):
129138
# will force the loop to finish processing requests before it ends.
130139
cleanup = True
131140
while cleanup or not self.request_queue.empty():
132-
cleanup = False
141+
cleanup, resp_queue, result = False, None, None
133142
try:
134-
response_queue = None
135143
while (self.tk_widget is not None and
136144
self._hook_status != TKHOOK_UNHOOKED) or cleanup:
137-
response_queue = None
145+
resp_queue = result = None
138146
# get the response container, function and args, call
139147
# the function and place the result into the response
140-
item = self.request_queue.get_nowait()
141-
response_queue, func, a, kw = item
142-
result = func(*a, **kw)
143-
response_queue.put((result, False))
144-
148+
resp_queue, func, a, kw = self.request_queue.get_nowait()
149+
result = (func(*a, **kw), False)
150+
break
145151
except _Empty:
146152
# nothing to process. break out of loop
147153
pass
148-
except Exception as e:
149-
if response_queue is not None:
150-
response_queue.put((e, True))
154+
except BaseException as e:
155+
if resp_queue is None:
156+
print(format_exception(e))
157+
else:
158+
result = (e, True)
151159

160+
result and resp_queue.put(result)
152161
if self._hook_status == TKHOOK_UNHOOKING:
153162
self.tk_widget.tk = self.tk_widget._tk
154163
self._hook_status = TKHOOK_UNHOOKED
@@ -181,13 +190,12 @@ def _tk_destroy_override(self, *a, **kw):
181190
self.tk_widget.after_cancel(cmd)
182191

183192

184-
185193
# dont hook twice or we'll end up with an infinite loop
186-
if not hasattr(Tk, "_orig_init"):
194+
if not(hasattr(Tk, "_orig_init") or ENV_DISABLED):
187195
Tk._orig_init = Tk.__init__
188196
Tk.__init__ = _tk_init_override
189197

190198

191-
if not hasattr(Tk, "_orig_destroy"):
199+
if not(hasattr(Tk, "_orig_destroy") or ENV_DISABLED):
192200
Tk._orig_destroy = Tk.destroy
193201
Tk.destroy = _tk_destroy_override

0 commit comments

Comments
 (0)