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

Experimental async #401

Merged
merged 10 commits into from
Oct 9, 2024
50 changes: 50 additions & 0 deletions dialect/asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import asyncio
import contextlib
import functools
from typing import Callable, Coroutine

from gi.events import GLibEventLoopPolicy


@contextlib.contextmanager
def glib_event_loop_policy():
original = asyncio.get_event_loop_policy()
policy = GLibEventLoopPolicy()
asyncio.set_event_loop_policy(policy)
try:
yield policy
finally:
asyncio.set_event_loop_policy(original)


_background_tasks: set[asyncio.Task] = set()


def create_background_task(coro: Coroutine) -> asyncio.Task:
"""
Create and track a task.

Normally tasks are weak-referenced by asyncio.
We keep track of them, so they can be completed before GC kicks in.
"""
task = asyncio.create_task(coro)
_background_tasks.add(task)
task.add_done_callback(_background_tasks.discard)
return task


def background_task(f: Callable[..., Coroutine]):
"""
Wraps an async function to be run using ``create_background_task``.

Useful to use async functions like signal handlers or GTK template callbacks.

Note: The return value will be lost, so this is not suitable when you need to
return something from the coroutine, what might be needed in some signal handlers.
"""

@functools.wraps(f)
def decor(*args, **kwargs):
create_background_task(f(*args, **kwargs))

return decor
13 changes: 8 additions & 5 deletions dialect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
except ImportError or ValueError:
logging.error("Error: GObject dependencies not met.")

from dialect.asyncio import glib_event_loop_policy
from dialect.define import APP_ID, RES_PATH, VERSION
from dialect.preferences import DialectPreferencesDialog
from dialect.settings import Settings
Expand Down Expand Up @@ -168,10 +169,7 @@ def _on_pronunciation(self, action: Gio.SimpleAction, value: GLib.Variant):

# Update UI
if self.window:
if self.window.trans_src_pron is not None:
self.window.src_pron_revealer.props.reveal_child = value # type: ignore
if self.window.trans_dest_pron is not None:
self.window.dest_pron_revealer.props.reveal_child = value # type: ignore
self.window._check_pronunciation()

def _on_preferences(self, _action, _param):
"""Show preferences window"""
Expand All @@ -198,4 +196,9 @@ def _on_quit(self, _action, _param):
def main():
# Run the Application
app = Dialect()
return app.run(sys.argv)
exit_code = 0

with glib_event_loop_policy():
exit_code = app.run(sys.argv)

return exit_code
1 change: 1 addition & 0 deletions dialect/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ subdir('search_provider')
# Python sources
sources = [
'__init__.py',
'asyncio.py',
'languages.py',
'main.py',
'preferences.py',
Expand Down
17 changes: 15 additions & 2 deletions dialect/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@
from dialect.providers.base import ( # noqa
BaseProvider,
ProviderCapability,
ProviderError,
ProviderErrorCode,
ProviderFeature,
Translation,
TranslationMistake,
TranslationPronunciation,
TranslationRequest,
)
from dialect.providers.errors import ( # noqa
APIKeyInvalid,
APIKeyRequired,
BatchSizeExceeded,
CharactersLimitExceeded,
InvalidLangCode,
ProviderError,
RequestError,
ServiceLimitReached,
UnexpectedError,
)

MODULES: dict[str, type[BaseProvider]] = {}
Expand Down
Loading
Loading