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.
This commit is contained in:
miruka 2019-12-18 06:44:18 -04:00
parent 5d7d66f99b
commit 9e372d01d5
7 changed files with 46 additions and 56 deletions

View File

@ -1 +1 @@
from .app import APP # noqa
from .qml_bridge import BRIDGE # noqa

View File

@ -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)

View File

@ -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

View File

@ -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__(

View File

@ -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()

View File

@ -24,7 +24,7 @@ Item {
HShortcut {
enabled: debugMode
sequences: settings.keys.startPythonDebugger
onActivated: py.call("APP.pdb")
onActivated: py.call("BRIDGE.pdb")
}
HShortcut {

View File

@ -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
})
})