Skip to content

Commit

Permalink
Merge pull request #6 from davidhozic/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
davidhozic authored Sep 25, 2023
2 parents 32c02c5 + 9f9d1ae commit 78090d8
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 19 deletions.
10 changes: 8 additions & 2 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,17 @@ Glossary
---------------------
Releases
---------------------
v1.2.0
===========
- Added ``show_exceptions`` parameter to :func:`~tk_async_execute.utils.async_execute()` and
:class:`~tk_async_execute.widget.ExecutingAsyncWindow`.
- Added :py:attr:`~tk_async_execute.widget.ExecutingAsyncWindow.future`


v1.1.0
=================
- Instead of showing a coroutine error on screen if it ocurred while running a coroutine with
:func:`~tk_async_execute.utils.async_execute()`, raise the exception instead in :func:`tk_async_execute.async_execute()`.
- Instead of showing a coroutine exception on screen when using
:func:`~tk_async_execute.utils.async_execute()`, raise the exception.

v1.0.1
=================
Expand Down
4 changes: 2 additions & 2 deletions tk_async_execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
VERSION = "1.1.0"
VERSION = "1.2.0"

from .utils import *
from . import widget
from .widget import ExecutingAsyncWindow
15 changes: 9 additions & 6 deletions tk_async_execute/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ def async_execute(
visible: bool = True,
pop_up: bool = False,
callback: Optional[Callable] = None,
master: Any = None
show_exceptions: bool = True,
**kwargs
):
"""
Executes a coroutine inside asyncio event loop.
Expand Down Expand Up @@ -149,8 +150,12 @@ def async_execute(
callback: Optional[Callable]
Callback function to call with result afer coro has finished.
Defaults to None.
master: Any
The parent tkinter widget.
show_exception: Optional[bool]
If True, any exceptions that ocurred in ``coro`` will be display though a message box on screen.
If you want to obtain the exception though code, you can do so by setting parameter ``wait`` of this function
to True and then calling ``window.future.exception()``.
**kwargs
Any tkinter specific parameters to the TopLevel widget.
Returns
-----------
Expand All @@ -162,10 +167,8 @@ def async_execute(
Exception
Exception that occurred in ``coro`` (if it ocurred). Only raised if ``wait`` is True.
"""
window = ExecutingAsyncWindow(coro, visible, pop_up, callback, master=master)
window = ExecutingAsyncWindow(coro, visible, pop_up, callback, show_exceptions, **kwargs)
if wait:
window.wait_window()
if (exc := window.future.exception()) is not None:
raise exc

return window
46 changes: 37 additions & 9 deletions tk_async_execute/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"""
from typing import Coroutine, Callable, Optional
from threading import current_thread
from concurrent.futures import Future

from tkinter import ttk
import tkinter as tk
Expand All @@ -38,11 +39,16 @@
from . import doc


@doc.doc_category("Widgets", members=False)
@doc.doc_category("Widgets", members=True)
class ExecutingAsyncWindow(tk.Toplevel):
"""
Window that hovers while executing async methods.
.. versionchanged:: v1.2
- Removed ``*args`` parameter.
- Added ``show_exceptions`` parameter.
.. note::
Direct usage of this is not recommended. Use :func:`tk_async_execute.async_execute` instead.
Expand All @@ -59,9 +65,11 @@ class ExecutingAsyncWindow(tk.Toplevel):
Defaults to False.
callback: Optional[Callable]
Callback function to call with result afer coro has finished.
Defaults to None.
args: Any
Other positional arguments passed to :class:`tkinter.Toplevel`
Defaults to None
show_exceptions: Optional[bool]
If True, any exceptions that ocurred in ``coro`` will be display though a message box on screen.
If you want to obtain the exception though code, you can do so, by awaiting ``await Window.future`` and then
read the exception with Window.future.exception() function.
kwargs: Any
Other keyword arguments passed to :class:`tkinter.Toplevel`
Expand All @@ -78,14 +86,15 @@ def __init__(
visible: bool = True,
pop_up: bool = False,
callback: Optional[Callable] = None,
*args,
show_exceptions: bool = True,
**kwargs
):
loop = self.loop
if loop is None or not loop.is_running():
raise RuntimeError("Start the loop first with 'tk_async_execute.start()'")

super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.show_exceptions = show_exceptions
self.title("Async execution window")
self.resizable(False, False)
frame_main = ttk.Frame(self, padding=(10, 10))
Expand Down Expand Up @@ -116,8 +125,17 @@ def __init__(
self.old_stdout = sys.stdout
sys.stdout = self

self.future = future = asyncio.run_coroutine_threadsafe(coro, self.loop)
future.add_done_callback(lambda fut: self.after_idle(self.destroy))
self._future = future = asyncio.run_coroutine_threadsafe(coro, self.loop)
future.add_done_callback(lambda fut: self.after_idle(self.destroy, future))

@property
def future(self) -> Future:
"""
Returns concurrent.futures.Future object.
This can be used to eg. obtain the coroutine result (``future.result()``)
or the exception (``future.exception()``).
"""
return self._future

def flush(self):
pass
Expand All @@ -132,7 +150,17 @@ def write(self, text: str):

self.old_stdout.write(text)

def destroy(self) -> None:
def destroy(self, future: asyncio.Future = None) -> None:
if future is not None and (exc := future.exception()) is not None and self.show_exceptions:
# ttkbootstrap compatibility
title = f"{self.awaitable.__name__} error"
message = f"{exc}\n\n({type(exc).__name__})"
if "ttkbootstrap" in sys.modules:
from ttkbootstrap.dialogs.dialogs import Messagebox
Messagebox.show_error(message, title, self.master)
else:
Messagebox = messagebox.showerror(title, message, master=self.master)

sys.stdout = self.old_stdout

if self.callback is not None:
Expand Down

0 comments on commit 78090d8

Please sign in to comment.