tkinterweb 4.17.2__py3-none-any.whl → 4.17.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tkinterweb/bindings.py CHANGED
@@ -532,13 +532,13 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
532
532
  self.queue_after = None
533
533
  self.queue = None
534
534
 
535
- def post_to_queue(self, callback):
535
+ def post_to_queue(self, callback, thread_safe=True):
536
536
  """Use this method to send a callback to TkinterWeb's thread-safety queue. The callback will be evaluated on the main thread.
537
537
  Use this when running Tkinter commands from within a thread.
538
538
  If the queue is not running (i.e. threading is disabled), the callback will be evaluated immediately.
539
539
 
540
540
  New in version 4.9."""
541
- if self.queue:
541
+ if thread_safe and self.queue:
542
542
  self.queue.put(callback)
543
543
  else:
544
544
  callback()
@@ -546,6 +546,7 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
546
546
  def post_event(self, event, thread_safe=False):
547
547
  "Generate a virtual event."
548
548
  # NOTE: when thread_safe=True, this method is thread-safe
549
+ # Would you believe that?
549
550
  if not self.events_enabled:
550
551
  return
551
552
 
@@ -565,6 +566,7 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
565
566
  def post_message(self, message, thread_safe=False):
566
567
  "Post a message."
567
568
  # NOTE: when thread_safe=True, this method is thread-safe
569
+ # Amazing stuff, eh?
568
570
  if not self.messages_enabled:
569
571
  return
570
572
 
@@ -722,16 +724,16 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
722
724
  else:
723
725
  return utilities.cache_download(url, *args, insecure=self.insecure_https, cafile=self.ssl_cafile, headers=tuple(self.headers.items()), timeout=self.request_timeout)
724
726
 
725
- def _thread_check(self, callback, *args, **kwargs):
727
+ def _thread_check(self, callback, url, *args, **kwargs):
726
728
  if not self.downloads_have_occured:
727
729
  self.downloads_have_occured = True
728
730
 
729
- if not self.threading_enabled:
730
- callback(*args, **kwargs)
731
+ if not self.threading_enabled or url.startswith("file://"):
732
+ callback(url, *args, **kwargs)
731
733
  elif len(self.active_threads) >= self.maximum_thread_count:
732
- self.after(500, lambda callback=callback, args=args: self._thread_check(callback, *args, **kwargs))
734
+ self.after(500, lambda callback=callback, url=url, args=args: self._thread_check(callback, url, *args, **kwargs))
733
735
  else:
734
- thread = utilities.StoppableThread(target=callback, args=args, kwargs=kwargs)
736
+ thread = utilities.StoppableThread(target=callback, args=(url, *args,), kwargs=kwargs)
735
737
  thread.start()
736
738
 
737
739
  def _begin_download(self):
@@ -747,9 +749,9 @@ It is likely that not all dependencies are installed. Make sure Cairo is install
747
749
 
748
750
  self.active_threads.remove(thread)
749
751
  if len(self.active_threads) == 0:
750
- self.post_to_queue(self._handle_load_finish)
752
+ self.post_to_queue(self._handle_load_finish, thread.is_subthread)
751
753
  else:
752
- self.post_to_queue(lambda: self._handle_load_finish(False))
754
+ self.post_to_queue(lambda: self._handle_load_finish(False), thread.is_subthread)
753
755
 
754
756
  def _finish_resource_load(self, message, url, resource, success):
755
757
  # NOTE: this must run in the main thread
tkinterweb/handlers.py CHANGED
@@ -471,7 +471,7 @@ class ScriptManager(utilities.BaseManager):
471
471
  attributes = dict(zip(attributes[::2], attributes[1::2])) # Make attributes a dict
472
472
 
473
473
  if "src" in attributes:
474
- self.html._thread_check(self.fetch_scripts, attributes, self.html.resolve_url(attributes["src"]))
474
+ self.html._thread_check(self.fetch_scripts, self.html.resolve_url(attributes["src"]), attributes)
475
475
  elif "defer" in attributes:
476
476
  self.pending_scripts.append((attributes, tag_contents))
477
477
  else:
@@ -484,22 +484,22 @@ class ScriptManager(utilities.BaseManager):
484
484
  thread = self.html._begin_download()
485
485
 
486
486
  if url and thread.isrunning():
487
- self.html.post_message(f"Fetching script from {utilities.shorten(url)}", True)
487
+ self.html.post_message(f"Fetching script from {utilities.shorten(url)}", thread.is_subthread)
488
488
  try:
489
489
  data = self.html.download_url(url)[1]
490
490
  except Exception as error:
491
491
  self.html.post_to_queue(lambda message=f"ERROR: could not load script {url}: {error}",
492
- url=url: self.html._finish_resource_load(message, url, "script", False))
492
+ url=url: self.html._finish_resource_load(message, url, "script", False), thread.is_subthread)
493
493
 
494
494
  if data and thread.isrunning():
495
495
  if "defer" in attributes:
496
496
  self.pending_scripts.append((attributes, data))
497
497
  else:
498
- self.html.post_to_queue(lambda attributes=attributes, data=data: self.html.on_script(attributes, data))
498
+ self.html.post_to_queue(lambda attributes=attributes, data=data: self.html.on_script(attributes, data), thread.is_subthread)
499
499
 
500
500
  if url:
501
501
  self.html.post_to_queue(lambda message=f"Successfully loaded {utilities.shorten(url)}",
502
- url=url: self.html._finish_resource_load(message, url, "script", True))
502
+ url=url: self.html._finish_resource_load(message, url, "script", True), thread.is_subthread)
503
503
 
504
504
  self.html._finish_download(thread)
505
505
 
@@ -532,14 +532,9 @@ class StyleManager(utilities.BaseManager):
532
532
  except tk.TclError:
533
533
  return
534
534
 
535
- if (
536
- ("stylesheet" in rel)
537
- and ("all" in media or "screen" in media)
538
- ):
539
- if url.startswith("file://"):
540
- self.fetch_styles(node, url)
541
- else:
542
- self.html._thread_check(self.fetch_styles, node, url)
535
+ if (("stylesheet" in rel)
536
+ and ("all" in media or "screen" in media)):
537
+ self.html._thread_check(self.fetch_styles, url, node)
543
538
  # Onload is fired if and when the stylesheet is parsed
544
539
  elif "icon" in rel:
545
540
  self.html.icon = url
@@ -553,8 +548,7 @@ class StyleManager(utilities.BaseManager):
553
548
  try:
554
549
  new_url = self.html.resolve_url(new_url, parent_url)
555
550
  self.html.post_message(f"Loading stylesheet from {utilities.shorten(new_url)}")
556
-
557
- self.html._thread_check(self.fetch_styles, url=new_url)
551
+ self.html._thread_check(self.fetch_styles, new_url)
558
552
 
559
553
  except Exception as error:
560
554
  self.html.post_message(f"ERROR: could not load stylesheet {new_url}: {error}")
@@ -567,21 +561,21 @@ class StyleManager(utilities.BaseManager):
567
561
  newurl = f"url('{newurl}')"
568
562
  return newurl
569
563
 
570
- def fetch_styles(self, node=None, url=None):
564
+ def fetch_styles(self, url=None, node=None):
571
565
  "Fetch stylesheets and parse the CSS code they contain"
572
566
  # NOTE: this runs in a thread
573
567
 
574
568
  thread = self.html._begin_download()
575
569
  if url and thread.isrunning():
576
- self.html.post_message(f"Fetching stylesheet from {utilities.shorten(url)}", True)
570
+ self.html.post_message(f"Fetching stylesheet from {utilities.shorten(url)}", thread.is_subthread)
577
571
  try:
578
572
  data = self.html.download_url(url)[1]
579
573
  if data and thread.isrunning():
580
- self.html.post_to_queue(lambda node=node, url=url, data=data: self._finish_fetching_styles(node, url, data))
574
+ self.html.post_to_queue(lambda node=node, url=url, data=data: self._finish_fetching_styles(node, url, data), thread.is_subthread)
581
575
 
582
576
  except Exception as error:
583
577
  self.html.post_to_queue(lambda message=f"ERROR: could not load stylesheet {url}: {error}",
584
- url=url: self.html._finish_resource_load(message, url, "stylesheet", False))
578
+ url=url: self.html._finish_resource_load(message, url, "stylesheet", False), thread.is_subthread)
585
579
 
586
580
  self.html._finish_download(thread)
587
581
 
@@ -651,7 +645,7 @@ class ImageManager(utilities.BaseManager):
651
645
  self.bad_paths.add(url)
652
646
 
653
647
  if not self.html.ignore_invalid_images:
654
- image, data_is_image = self.check_images(utilities.BROKEN_IMAGE, name, url, "image/png")
648
+ image, data_is_image = self.check_images(utilities.BROKEN_IMAGE, name, url, "image/png", False)
655
649
  image = imageutils.data_to_image(image, name, "image/png", data_is_image)
656
650
 
657
651
  self.loaded_images.setdefault(name, set()).add(image)
@@ -692,10 +686,7 @@ class ImageManager(utilities.BaseManager):
692
686
  else:
693
687
  url = url.split("), url(", 1)[0].replace("'", "").replace('"', "")
694
688
  url = self.html.resolve_url(url)
695
- if url.startswith("file://"):
696
- self.fetch_images(url, name)
697
- else:
698
- self.html._thread_check(self.fetch_images, url, name)
689
+ self.html._thread_check(self.fetch_images, url, name)
699
690
 
700
691
  return list((name, self.html.register(self._on_image_delete)))
701
692
 
@@ -705,23 +696,26 @@ class ImageManager(utilities.BaseManager):
705
696
 
706
697
  thread = self.html._begin_download()
707
698
  if thread.isrunning():
708
- self.html.post_message(f"Fetching image from {utilities.shorten(url)}", True)
699
+ self.html.post_message(f"Fetching image from {utilities.shorten(url)}", thread.is_subthread)
709
700
 
710
701
  if url == self.html.base_url:
711
- self.html.post_to_queue(lambda url=url, name=name, error="ERROR: image url not specified": self._on_image_error(url, name, error))
702
+ self.html.post_to_queue(lambda url=url, name=name, error="ERROR: image url not specified":
703
+ self._on_image_error(url, name, error), thread.is_subthread)
712
704
  else:
713
705
  try:
714
706
  url, data, filetype, code = self.html.download_url(url)
715
- data, data_is_image = self.check_images(data, name, url, filetype)
707
+ data, data_is_image = self.check_images(data, name, url, filetype, thread.is_subthread)
716
708
 
717
709
  if thread.isrunning():
718
- self.html.post_to_queue(lambda data=data, name=name, url=url, filetype=filetype, data_is_image=data_is_image: self.finish_fetching_images(data, name, url, filetype, data_is_image))
710
+ self.html.post_to_queue(lambda data=data, name=name, url=url, filetype=filetype, data_is_image=data_is_image:
711
+ self.finish_fetching_images(data, name, url, filetype, data_is_image), thread.is_subthread)
719
712
  except Exception as error:
720
- self.html.post_to_queue(lambda url=url, name=name, error=f"ERROR: could not load image {url}: {error}": self._on_image_error(url, name, error))
713
+ self.html.post_to_queue(lambda url=url, name=name, error=f"ERROR: could not load image {url}: {error}":
714
+ self._on_image_error(url, name, error), thread.is_subthread)
721
715
 
722
716
  self.html._finish_download(thread)
723
717
 
724
- def check_images(self, data, name, url, filetype):
718
+ def check_images(self, data, name, url, filetype, thread_safe):
725
719
  "Invert images if needed and convert SVG images to PNGs."
726
720
  # NOTE: this runs in a thread
727
721
 
@@ -738,7 +732,7 @@ class ImageManager(utilities.BaseManager):
738
732
  data_is_image = True
739
733
  except (ImportError, ModuleNotFoundError,):
740
734
  error = f"ERROR: could not invert the image {url}: PIL and PIL.ImageTk must be installed."
741
- self.html.post_to_queue(lambda url=url, name=name, error=error: self._on_image_error(url, name, error))
735
+ self.html.post_to_queue(lambda url=url, name=name, error=error: self._on_image_error(url, name, error), thread_safe)
742
736
 
743
737
  return data, data_is_image
744
738
 
@@ -893,10 +887,7 @@ class ObjectManager(utilities.BaseManager):
893
887
  return
894
888
 
895
889
  self.html.post_message(f"Creating object from {utilities.shorten(data)}")
896
- if data.startswith("file://"):
897
- self.fetch_objects(node, data, straight=True)
898
- else:
899
- self.html._thread_check(self.fetch_objects, node, data)
890
+ self.html._thread_check(self.fetch_objects, data, node)
900
891
 
901
892
  def _on_object_value_change(self, node, attribute, value):
902
893
  if attribute == "data":
@@ -907,7 +898,7 @@ class ObjectManager(utilities.BaseManager):
907
898
  # Force reset because it might contain widgets that are added internally
908
899
  self.html.widget_manager.map_node(node, True)
909
900
 
910
- def fetch_objects(self, node, url, straight=False):
901
+ def fetch_objects(self, url, node):
911
902
  # NOTE: this runs in a thread
912
903
 
913
904
  thread = self.html._begin_download()
@@ -919,16 +910,12 @@ class ObjectManager(utilities.BaseManager):
919
910
  if data and thread.isrunning():
920
911
  if filetype.startswith("image"):
921
912
  name = self.html.image_manager.allocate_image_name()
922
- data, data_is_image = self.html.image_manager.check_images(data, name, url, filetype)
923
- if straight:
924
- self._finish_fetching_image_objects(node, data, name, url, filetype, data_is_image)
925
- else:
926
- self.html.post_to_queue(lambda node=node, data=data, name=name, url=url, filetype=filetype, data_is_image=data_is_image: self._finish_fetching_image_objects(node, data, name, url, filetype, data_is_image))
913
+ data, data_is_image = self.html.image_manager.check_images(data, name, url, filetype, thread.is_subthread)
914
+ self.html.post_to_queue(lambda node=node, data=data, name=name, url=url, filetype=filetype, data_is_image=data_is_image:
915
+ self._finish_fetching_image_objects(node, data, name, url, filetype, data_is_image), thread.is_subthread)
927
916
  elif filetype == "text/html":
928
- if straight:
929
- self._finish_fetching_HTML_objects(node, data, url, filetype)
930
- else:
931
- self.html.post_to_queue(lambda node=node, data=data, url=url, filetype=filetype: self._finish_fetching_HTML_objects(node, data, url, filetype))
917
+ self.html.post_to_queue(lambda node=node, data=data, url=url, filetype=filetype:
918
+ self._finish_fetching_HTML_objects(node, data, url, filetype), thread.is_subthread)
932
919
 
933
920
  except Exception as error:
934
921
  self.html.post_message(f"ERROR: could not load object element with data {url}: {error}", True)
tkinterweb/htmlwidgets.py CHANGED
@@ -1223,6 +1223,8 @@ class HtmlFrame(Frame):
1223
1223
  url = url.replace("view-source:", "")
1224
1224
  parsed = urlparse(url)
1225
1225
 
1226
+ thread = utilities.get_current_thread()
1227
+
1226
1228
  location = parsed.netloc if parsed.netloc else parsed.path
1227
1229
  self._html.post_message(f"Connecting to {location}", True)
1228
1230
  if self._html.insecure_https: self._html.post_message("WARNING: Using insecure HTTPS session", True)
@@ -1230,7 +1232,7 @@ class HtmlFrame(Frame):
1230
1232
  newurl, data, filetype, code = self._html.download_url(url, data, method, decode)
1231
1233
  self._html.post_message(f"Successfully connected to {location}", True)
1232
1234
 
1233
- if utilities.get_current_thread().isrunning():
1235
+ if thread.isrunning():
1234
1236
  if view_source:
1235
1237
  newurl = "view-source:"+newurl
1236
1238
  if self._current_url != newurl:
@@ -1250,7 +1252,7 @@ class HtmlFrame(Frame):
1250
1252
  elif "image" in filetype:
1251
1253
  name = self._html.image_manager.allocate_image_name()
1252
1254
  if name:
1253
- data, data_is_image = self._html.image_manager.check_images(data, name, url, filetype)
1255
+ data, data_is_image = self._html.image_manager.check_images(data, name, url, filetype, thread.is_subthread)
1254
1256
  self._html.post_to_queue(lambda data=data, name=name, url=url, filetype=filetype, data_is_image=data_is_image: self._finish_loading_image(data, name, url, filetype, data_is_image))
1255
1257
  else:
1256
1258
  self.load_html(self._get_about_page("about:image", name), newurl, _thread_safe=thread_safe)
tkinterweb/utilities.py CHANGED
@@ -31,7 +31,7 @@ __title__ = "TkinterWeb"
31
31
  __author__ = "Andrew Clarke"
32
32
  __copyright__ = "(c) 2021-2025 Andrew Clarke"
33
33
  __license__ = "MIT"
34
- __version__ = "4.17.2"
34
+ __version__ = "4.17.3"
35
35
 
36
36
 
37
37
  ROOT_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "resources")
@@ -686,6 +686,8 @@ class StoppableThread(threading.Thread):
686
686
  self.daemon = True
687
687
  self.running = True
688
688
 
689
+ self.is_subthread = True
690
+
689
691
  def stop(self):
690
692
  self.running = False
691
693
 
@@ -697,6 +699,9 @@ class PlaceholderThread:
697
699
  """Fake StoppableThread. The only purpose of this is to provide fake methods that mirror the StoppableThread class.
698
700
  This means that if a download is running in the MainThread, the stop flags can still be set without raising errors, though they won't do anything."""
699
701
 
702
+ def __init__(self, *args, **kwargs):
703
+ self.is_subthread = False
704
+
700
705
  def stop(self):
701
706
  return
702
707
 
@@ -855,14 +860,11 @@ def download(url, data=None, method="GET", decode=None, insecure=False, cafile=N
855
860
  parsed = urlparse(url)
856
861
  url = urlunparse(parsed._replace(query=""))
857
862
 
858
- thread = get_current_thread()
859
863
  url = url.replace(" ", "%20")
860
864
  if data and (method == "POST"):
861
865
  req = Request(url, data, headers=dict(headers))
862
866
  else:
863
867
  req = Request(url, headers=dict(headers))
864
- if not thread.isrunning():
865
- return None, url, "", ""
866
868
 
867
869
  with urlopen(req, context=context, timeout=timeout) as res:
868
870
  data = res.read()
@@ -893,10 +895,8 @@ def download(url, data=None, method="GET", decode=None, insecure=False, cafile=N
893
895
  data = data.decode(decode, errors="ignore")
894
896
  else:
895
897
  data = data.decode(errors="ignore")
896
- if not thread.isrunning():
897
- return url, None, "", code
898
- else:
899
- return url, data, filetype, code
898
+
899
+ return url, data, filetype, code
900
900
 
901
901
 
902
902
  @lru_cache()
@@ -915,6 +915,7 @@ def shorten(string):
915
915
  def get_current_thread():
916
916
  "Return the currently running thread"
917
917
  thread = threading.current_thread()
918
+ # Py 3.4+: Use is threading.main_thread()
918
919
  if thread.name == "MainThread":
919
920
  thread = PlaceholderThread()
920
921
  return thread
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tkinterweb
3
- Version: 4.17.2
3
+ Version: 4.17.3
4
4
  Summary: HTML/CSS viewer, editor, and app builder for Tkinter
5
5
  Home-page: https://github.com/Andereoo/TkinterWeb
6
6
  License: MIT
@@ -1,17 +1,17 @@
1
1
  tkinterweb/__init__.py,sha256=mKnt2RjNd0zMntvkmT4dn5iyytsko7fJJb-rnx4H82E,4690
2
- tkinterweb/bindings.py,sha256=4orI0ZHiMlYG_lMcNOxzFJS8hOWYvjuoPzYsPyEp1NM,73362
2
+ tkinterweb/bindings.py,sha256=XE0CPX28YtwBW9g9l0nsVVSHe3O3pX19w6POxorX8_k,73563
3
3
  tkinterweb/dom.py,sha256=6zEZUGMSdWuwu1-MQOpt1XJN-w_Aku1HGY5P2iM2ksE,43546
4
4
  tkinterweb/extensions.py,sha256=QfV13omkMoeXe17JPbeJjykwXtWTcZ24mKuB7NoBgVI,46555
5
- tkinterweb/handlers.py,sha256=iYzDgiHeF_wlXXPS2jI-sicq7KaGEDYq43fHbLZsaH0,42776
6
- tkinterweb/htmlwidgets.py,sha256=sLsF3aQKoVBomFyaPrAvwmWkZ5DgNP1Q1U46Fs_osdc,104317
5
+ tkinterweb/handlers.py,sha256=3ptECxVAiSarldfi4_C_mqd7A9xyTzpqvgwOKcBCX_I,42583
6
+ tkinterweb/htmlwidgets.py,sha256=OSgLFlT4TpMRWFAwLdGcnX9Mc8FBtx-yFCUlxdKBivs,104373
7
7
  tkinterweb/imageutils.py,sha256=Du6vX00Isx7z1Q6sVV4po9ycYrP_iDG22ScG04U_huQ,5368
8
8
  tkinterweb/js.py,sha256=Wlameh5KGK3GKzBCczaWYWT1L6-5uOWHt1HCZmzrjLk,3597
9
9
  tkinterweb/subwidgets.py,sha256=wT4gtUnUE5j9UA8lrKCjLaysWqHav-_46NF97uMswDo,28200
10
- tkinterweb/utilities.py,sha256=iRjqA3ULlLueVO38M9azbmIYk7RFtv98fmdA9SDWpwA,43463
10
+ tkinterweb/utilities.py,sha256=LFjs3MlfiIleVZMjFh5mTMk_6H3LXqxfXo_dQ02scTE,43431
11
11
  tkinterweb/resources/combobox-2.3.tm,sha256=flofzRxHJMeEj0I--4iLT9SWIo9G1MSIwY0h940tG-U,65083
12
12
  tkinterweb/resources/pkgIndex.tcl,sha256=Zp99aTL170d0TbVrWS-6uzaIaykUQbRO0krvmJhcduA,77
13
- tkinterweb-4.17.2.dist-info/LICENSE.md,sha256=Jf8BR3uTR9RLMAA2osul_ZRkvoYRgmlqq1Pd-xt0uMY,1074
14
- tkinterweb-4.17.2.dist-info/METADATA,sha256=aULx21SFHyyc_nV0YaNCSclu4IT0mA6sgCgXwagzOhI,3133
15
- tkinterweb-4.17.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
16
- tkinterweb-4.17.2.dist-info/top_level.txt,sha256=QVZQjAzSgzBGwOl41qcOyvkYAwy1FXLlnvwtYvE9Ur4,11
17
- tkinterweb-4.17.2.dist-info/RECORD,,
13
+ tkinterweb-4.17.3.dist-info/LICENSE.md,sha256=Jf8BR3uTR9RLMAA2osul_ZRkvoYRgmlqq1Pd-xt0uMY,1074
14
+ tkinterweb-4.17.3.dist-info/METADATA,sha256=KdU710Bje-ndhFi-Oq1vVdfRiz6cuqczy2ve8NJKVNg,3133
15
+ tkinterweb-4.17.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
16
+ tkinterweb-4.17.3.dist-info/top_level.txt,sha256=QVZQjAzSgzBGwOl41qcOyvkYAwy1FXLlnvwtYvE9Ur4,11
17
+ tkinterweb-4.17.3.dist-info/RECORD,,