From 9e372d01d50bafbc54a03f6a07bbb6a11585ff07 Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 18 Dec 2019 06:44:18 -0400 Subject: [PATCH] Python backend "App" becomes QmlBridge Is now strictly about setting up asyncio/uvloop and providing the functions for QML to interact with it and call backend coroutines. The appdirs attribute is moved to Backend. --- src/backend/__init__.py | 2 +- src/backend/backend.py | 14 ++++-- src/backend/config_files.py | 8 ++-- src/backend/matrix_client.py | 2 +- src/backend/{app.py => qml_bridge.py} | 66 ++++++++++----------------- src/gui/GlobalShortcuts.qml | 2 +- src/gui/PythonBridge/PythonBridge.qml | 8 ++-- 7 files changed, 46 insertions(+), 56 deletions(-) rename src/backend/{app.py => qml_bridge.py} (54%) diff --git a/src/backend/__init__.py b/src/backend/__init__.py index b3fcac61..2c631a9e 100644 --- a/src/backend/__init__.py +++ b/src/backend/__init__.py @@ -1 +1 @@ -from .app import APP # noqa +from .qml_bridge import BRIDGE # noqa diff --git a/src/backend/backend.py b/src/backend/backend.py index ed82b809..0656aca4 100644 --- a/src/backend/backend.py +++ b/src/backend/backend.py @@ -1,22 +1,28 @@ import asyncio import logging as log +import sys from pathlib import Path from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple import hsluv +from appdirs import AppDirs import nio -from .app import App +from . import __about__ from .errors import MatrixError from .matrix_client import MatrixClient from .models.items import Account, Device, Event, Member, Room, Upload from .models.model_store import ModelStore +log.getLogger().setLevel(log.INFO) +nio.logger_group.level = nio.log.logbook.ERROR +nio.log.logbook.StreamHandler(sys.stderr).push_application() + class Backend: - def __init__(self, app: App) -> None: - self.app = app + def __init__(self) -> None: + self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True) from . import config_files self.saved_accounts = config_files.Accounts(self) @@ -43,7 +49,7 @@ class Backend: DefaultDict(asyncio.Lock) # {room_id: lock} from .media_cache import MediaCache - cache_dir = Path(self.app.appdirs.user_cache_dir) + cache_dir = Path(self.appdirs.user_cache_dir) self.media_cache = MediaCache(self, cache_dir) diff --git a/src/backend/config_files.py b/src/backend/config_files.py index a8f75c1b..19e9e33f 100644 --- a/src/backend/config_files.py +++ b/src/backend/config_files.py @@ -30,7 +30,7 @@ class ConfigFile: @property def path(self) -> Path: - return Path(self.backend.app.appdirs.user_config_dir) / self.filename + return Path(self.backend.appdirs.user_config_dir) / self.filename async def default_data(self): @@ -180,7 +180,7 @@ class UIState(JSONConfigFile): @property def path(self) -> Path: - return Path(self.backend.app.appdirs.user_data_dir) / self.filename + return Path(self.backend.appdirs.user_data_dir) / self.filename async def default_data(self) -> JsonData: @@ -198,7 +198,7 @@ class History(JSONConfigFile): @property def path(self) -> Path: - return Path(self.backend.app.appdirs.user_data_dir) / self.filename + return Path(self.backend.appdirs.user_data_dir) / self.filename async def default_data(self) -> JsonData: @@ -209,7 +209,7 @@ class History(JSONConfigFile): class Theme(ConfigFile): @property def path(self) -> Path: - data_dir = Path(self.backend.app.appdirs.user_data_dir) + data_dir = Path(self.backend.appdirs.user_data_dir) return data_dir / "themes" / self.filename diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 50b6fe17..3450c648 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -69,7 +69,7 @@ class MatrixClient(nio.AsyncClient): f"homeserver is missing scheme (e.g. https://): {homeserver}", ) - store = Path(backend.app.appdirs.user_data_dir) / "encryption" + store = Path(backend.appdirs.user_data_dir) / "encryption" store.mkdir(parents=True, exist_ok=True) super().__init__( diff --git a/src/backend/app.py b/src/backend/qml_bridge.py similarity index 54% rename from src/backend/app.py rename to src/backend/qml_bridge.py index c4c0e7f1..f797ec6d 100644 --- a/src/backend/app.py +++ b/src/backend/qml_bridge.py @@ -1,56 +1,37 @@ import asyncio import logging as log import signal -import sys import traceback from concurrent.futures import Future from operator import attrgetter from threading import Thread from typing import Coroutine, Sequence -import nio -from appdirs import AppDirs - -from . import __about__ +from .backend import Backend from .pyotherside_events import CoroutineDone -log.getLogger().setLevel(log.INFO) -nio.logger_group.level = nio.log.logbook.ERROR -nio.log.logbook.StreamHandler(sys.stderr).push_application() - try: import uvloop except ModuleNotFoundError: - UVLOOP = False - log.info("uvloop not available, using default asyncio loop.") + log.warning("uvloop module not found, using slower default asyncio loop") else: - UVLOOP = True - log.info("uvloop is available.") + uvloop.install() -class App: +class QmlBridge: def __init__(self) -> None: - self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True) + self.backend = Backend() - from .backend import Backend - self.backend = Backend(app=self) - self.debug = False - - self.loop = asyncio.get_event_loop() - self.loop_thread = Thread(target=self._loop_starter) - self.loop_thread.start() + self.loop = asyncio.get_event_loop() + Thread(target=self._start_loop_in_thread).start() - def _loop_starter(self) -> None: + def _start_loop_in_thread(self) -> None: asyncio.set_event_loop(self.loop) - - if UVLOOP: - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) - self.loop.run_forever() - def run_in_loop(self, coro: Coroutine) -> Future: + def _run_coro_in_loop(self, coro: Coroutine) -> Future: return asyncio.run_coroutine_threadsafe(coro, self.loop) @@ -66,37 +47,40 @@ class App: CoroutineDone(uuid, result, exception, trace) - future = self.run_in_loop(coro) + future = self._run_coro_in_loop(coro) future.add_done_callback(on_done) return future - def call_backend_coro(self, name: str, uuid: str, args: Sequence[str] = (), - ) -> Future: + def call_backend_coro( + self, name: str, uuid: str, args: Sequence[str] = (), + ) -> Future: return self._call_coro(attrgetter(name)(self.backend)(*args), uuid) - def call_client_coro(self, - account_id: str, - name: str, - uuid: str, - args: Sequence[str] = ()) -> Future: - client = self.backend.clients[account_id] + def call_client_coro( + self, user_id: str, name: str, uuid: str, args: Sequence[str] = (), + ) -> Future: + + client = self.backend.clients[user_id] return self._call_coro(attrgetter(name)(client)(*args), uuid) def pdb(self, additional_data: Sequence = ()) -> None: ad = additional_data # noqa - rl = self.run_in_loop # noqa + rc = self._run_coro_in_loop # noqa ba = self.backend # noqa mo = self.backend.models # noqa cl = self.backend.clients - tcl = lambda user: cl[f"@{user}:matrix.org"] # noqa + gcl = lambda user: cl[f"@{user}:matrix.org"] # noqa from .models.items import Account, Room, Member, Event, Device # noqa p = print # pdb's `p` doesn't print a class's __str__ # noqa - from pprintpp import pprint as pp # noqa + try: + from pprintpp import pprint as pp # noqa + except ModuleNotFoundError: + pass log.info("\n=> Run `socat readline tcp:127.0.0.1:4444` in a terminal " "to connect to pdb.") @@ -107,4 +91,4 @@ class App: # Make CTRL-C work again signal.signal(signal.SIGINT, signal.SIG_DFL) -APP = App() +BRIDGE = QmlBridge() diff --git a/src/gui/GlobalShortcuts.qml b/src/gui/GlobalShortcuts.qml index 2819841b..7c18293c 100644 --- a/src/gui/GlobalShortcuts.qml +++ b/src/gui/GlobalShortcuts.qml @@ -24,7 +24,7 @@ Item { HShortcut { enabled: debugMode sequences: settings.keys.startPythonDebugger - onActivated: py.call("APP.pdb") + onActivated: py.call("BRIDGE.pdb") } HShortcut { diff --git a/src/gui/PythonBridge/PythonBridge.qml b/src/gui/PythonBridge/PythonBridge.qml index 9214f9f6..ee815079 100644 --- a/src/gui/PythonBridge/PythonBridge.qml +++ b/src/gui/PythonBridge/PythonBridge.qml @@ -12,7 +12,7 @@ Python { addImportPath("src") addImportPath("qrc:/src") - importNames("backend", ["APP"], () => { + importNames("backend", ["BRIDGE"], () => { loadSettings(() => { callCoro("saved_accounts.any_saved", [], any => { if (any) { py.callCoro("load_saved_accounts", []) } @@ -45,7 +45,7 @@ Python { function callSync(name, args=[]) { - return call_sync("APP.backend." + name, args) + return call_sync("BRIDGE.backend." + name, args) } @@ -56,7 +56,7 @@ Python { let future = privates.makeFuture() - call("APP.call_backend_coro", [name, uuid, args], pyFuture => { + call("BRIDGE.call_backend_coro", [name, uuid, args], pyFuture => { future.privates.pythonFuture = pyFuture }) @@ -76,7 +76,7 @@ Python { let call_args = [accountId, name, uuid, args] - call("APP.call_client_coro", call_args, pyFuture => { + call("BRIDGE.call_client_coro", call_args, pyFuture => { future.privates.pythonFuture = pyFuture }) })