Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge the handling of on_result and async result handling #2264

Merged
merged 10 commits into from
Dec 13, 2023
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