Skip to content

Commit

Permalink
Merge pull request #2264 from freakboy3742/async-result-cleanup
Browse files Browse the repository at this point in the history
Merge the handling of on_result and async result handling
  • Loading branch information
mhsmith authored Dec 13, 2023
2 parents 7801af8 + 92a1629 commit 36f1c75
Show file tree
Hide file tree
Showing 25 changed files with 654 additions and 436 deletions.
21 changes: 5 additions & 16 deletions android/src/toga_android/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,8 @@ def __init__(
positive_text,
negative_text=None,
icon=None,
on_result=None,
):
super().__init__(interface=interface)
self.on_result = on_result

self.native = AlertDialog.Builder(interface.window._impl.app.native)
self.native.setCancelable(False)
Expand All @@ -57,54 +55,49 @@ def __init__(
self.native.show()

def completion_handler(self, return_value: bool) -> None:
self.on_result(return_value)
self.interface.future.set_result(return_value)
self.interface.set_result(return_value)


class InfoDialog(TextDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
on_result=on_result,
)


class QuestionDialog(TextDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="Yes",
negative_text="No",
on_result=on_result,
)


class ConfirmDialog(TextDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
negative_text="Cancel",
on_result=on_result,
)


class ErrorDialog(TextDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
positive_text="OK",
icon=R.drawable.ic_dialog_alert,
on_result=on_result,
)


Expand All @@ -114,7 +107,6 @@ def __init__(
interface,
title,
message,
on_result=None,
**kwargs,
):
super().__init__(interface=interface)
Expand All @@ -129,7 +121,6 @@ def __init__(
filename,
initial_directory,
file_types=None,
on_result=None,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.save_file_dialog()")
Expand All @@ -143,7 +134,6 @@ def __init__(
initial_directory,
file_types,
multiple_select,
on_result=None,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.open_file_dialog()")
Expand All @@ -156,7 +146,6 @@ def __init__(
title,
initial_directory,
multiple_select,
on_result=None,
):
super().__init__(interface=interface)
interface.window.factory.not_implemented("Window.select_folder_dialog()")
23 changes: 6 additions & 17 deletions android/src/toga_android/widgets/webview.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,17 @@


class ReceiveString(dynamic_proxy(ValueCallback)):
def __init__(self, future, on_result):
def __init__(self, result):
super().__init__()
self.future = future
self.on_result = on_result
self.result = result

def onReceiveValue(self, value):
# If the evaluation fails, a message is written to Logcat, but the value sent to
# the callback will be "null", with no way to distinguish it from an actual null
# return value.
result = json.loads(value)
res = json.loads(value)

# Because this method is called directly from the Android event loop, calling
# set_result on a timed-out future would crash the whole testbed with an
# InvalidStateError.
if self.future.cancelled(): # pragma: nocover
pass
else:
self.future.set_result(result)
if self.on_result:
self.on_result(result)
self.result.set_result(res)


class WebView(Widget):
Expand Down Expand Up @@ -77,9 +68,7 @@ def set_user_agent(self, value):
)

def evaluate_javascript(self, javascript, on_result=None):
result = JavaScriptResult()
result = JavaScriptResult(on_result)

self.native.evaluateJavascript(
javascript, ReceiveString(result.future, on_result)
)
self.native.evaluateJavascript(javascript, ReceiveString(result))
return result
1 change: 1 addition & 0 deletions changes/2264.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Synchronous ``on_result`` handling was merged into AsyncResult.
1 change: 1 addition & 0 deletions changes/2264.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The use of synchronous ``on_result`` callbacks on dialogs and ``Webview.evaluate_javascript()`` calls has been deprecated. These methods should be used in their asynchronous form.
39 changes: 10 additions & 29 deletions cocoa/src/toga_cocoa/dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ def __init__(
message,
alert_style,
completion_handler,
on_result=None,
**kwargs
**kwargs,
):
super().__init__(interface=interface)
self.on_result = on_result

self.native = NSAlert.alloc().init()
self.native.icon = interface.app.icon._impl.native
Expand All @@ -54,39 +52,31 @@ def build_dialog(self):
pass

def completion_handler(self, return_value: int) -> None:
self.on_result(None)

self.interface.future.set_result(None)
self.interface.set_result(None)

def bool_completion_handler(self, return_value: int) -> None:
result = return_value == NSAlertFirstButtonReturn

self.on_result(result)

self.interface.future.set_result(result)
self.interface.set_result(return_value == NSAlertFirstButtonReturn)


class InfoDialog(NSAlertDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
alert_style=NSAlertStyle.Informational,
completion_handler=self.completion_handler,
on_result=on_result,
)


class QuestionDialog(NSAlertDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
alert_style=NSAlertStyle.Informational,
completion_handler=self.bool_completion_handler,
on_result=on_result,
)

def build_dialog(self):
Expand All @@ -95,14 +85,13 @@ def build_dialog(self):


class ConfirmDialog(NSAlertDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
alert_style=NSAlertStyle.Informational,
completion_handler=self.bool_completion_handler,
on_result=on_result,
)

def build_dialog(self):
Expand All @@ -111,19 +100,18 @@ def build_dialog(self):


class ErrorDialog(NSAlertDialog):
def __init__(self, interface, title, message, on_result=None):
def __init__(self, interface, title, message):
super().__init__(
interface=interface,
title=title,
message=message,
alert_style=NSAlertStyle.Critical,
completion_handler=self.completion_handler,
on_result=on_result,
)


class StackTraceDialog(NSAlertDialog):
def __init__(self, interface, title, message, on_result=None, **kwargs):
def __init__(self, interface, title, message, **kwargs):
if kwargs.get("retry"):
completion_handler = self.bool_completion_handler
else:
Expand All @@ -135,7 +123,6 @@ def __init__(self, interface, title, message, on_result=None, **kwargs):
message=message,
alert_style=NSAlertStyle.Critical,
completion_handler=completion_handler,
on_result=on_result,
**kwargs,
)

Expand Down Expand Up @@ -216,18 +203,15 @@ def single_path_completion_handler(self, return_value: int) -> None:
else:
result = None

self.on_result(result)
self.interface.future.set_result(result)
self.interface.set_result(result)

def multi_path_completion_handler(self, return_value: int) -> None:
if return_value == NSModalResponseOK:
result = [Path(url.path) for url in self.selected_paths()]
else:
result = None

self.on_result(result)

self.interface.future.set_result(result)
self.interface.set_result(result)


class SaveFileDialog(FileDialog):
Expand All @@ -247,7 +231,6 @@ def __init__(
initial_directory=initial_directory,
file_types=None, # File types aren't offered by Cocoa save panels.
multiple_select=False,
on_result=on_result,
)

def create_panel(self, multiple_select):
Expand All @@ -271,7 +254,6 @@ def __init__(
initial_directory=initial_directory,
file_types=file_types,
multiple_select=multiple_select,
on_result=on_result,
)

def create_panel(self, multiple_select):
Expand All @@ -298,7 +280,6 @@ def __init__(
initial_directory=initial_directory,
file_types=None,
multiple_select=multiple_select,
on_result=on_result,
)

def create_panel(self, multiple_select):
Expand Down
18 changes: 5 additions & 13 deletions cocoa/src/toga_cocoa/widgets/webview.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@
from .base import Widget


def js_completion_handler(future, on_result=None):
def js_completion_handler(result):
def _completion_handler(res: objc_id, error: objc_id) -> None:
if error:
error = py_from_ns(error)
exc = RuntimeError(str(error))
future.set_exception(exc)
if on_result:
on_result(None, exception=exc)
result.set_exception(exc)
else:
result = py_from_ns(res)
future.set_result(result)
if on_result:
on_result(result)
result.set_result(py_from_ns(res))

return _completion_handler

Expand Down Expand Up @@ -84,13 +79,10 @@ def set_user_agent(self, value):
self.native.customUserAgent = value

def evaluate_javascript(self, javascript: str, on_result=None) -> str:
result = JavaScriptResult()
result = JavaScriptResult(on_result=on_result)
self.native.evaluateJavaScript(
javascript,
completionHandler=js_completion_handler(
future=result.future,
on_result=on_result,
),
completionHandler=js_completion_handler(result),
)

return result
Expand Down
31 changes: 30 additions & 1 deletion core/src/toga/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import inspect
import sys
import traceback
import warnings
from abc import ABC


Expand Down Expand Up @@ -112,10 +113,38 @@ def _handler(*args, **kwargs):


class AsyncResult(ABC):
def __init__(self):
def __init__(self, on_result=None):
loop = asyncio.get_event_loop()
self.future = loop.create_future()

######################################################################
# 2023-12: Backwards compatibility
######################################################################
if on_result:
warnings.warn(
"Synchronous `on_result` handlers have been deprecated; use `await` on the asynchronous result",
DeprecationWarning,
)

self.on_result = on_result
else:
self.on_result = None
######################################################################
# End backwards compatibility.
######################################################################

def set_result(self, result):
if not self.future.cancelled():
self.future.set_result(result)
if self.on_result:
self.on_result(result)

def set_exception(self, exc):
if not self.future.cancelled():
self.future.set_exception(exc)
if self.on_result:
self.on_result(None, exception=exc)

def __repr__(self):
return f"<Async {self.RESULT_TYPE} result; future={self.future}>"

Expand Down
Loading

0 comments on commit 36f1c75

Please sign in to comment.