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 asyncio
import logging as log import logging as log
import sys
from pathlib import Path from pathlib import Path
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple
import hsluv import hsluv
from appdirs import AppDirs
import nio import nio
from .app import App from . import __about__
from .errors import MatrixError from .errors import MatrixError
from .matrix_client import MatrixClient from .matrix_client import MatrixClient
from .models.items import Account, Device, Event, Member, Room, Upload from .models.items import Account, Device, Event, Member, Room, Upload
from .models.model_store import ModelStore 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: class Backend:
def __init__(self, app: App) -> None: def __init__(self) -> None:
self.app = app self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True)
from . import config_files from . import config_files
self.saved_accounts = config_files.Accounts(self) self.saved_accounts = config_files.Accounts(self)
@ -43,7 +49,7 @@ class Backend:
DefaultDict(asyncio.Lock) # {room_id: lock} DefaultDict(asyncio.Lock) # {room_id: lock}
from .media_cache import MediaCache 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) self.media_cache = MediaCache(self, cache_dir)

View File

@ -30,7 +30,7 @@ class ConfigFile:
@property @property
def path(self) -> Path: 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): async def default_data(self):
@ -180,7 +180,7 @@ class UIState(JSONConfigFile):
@property @property
def path(self) -> Path: 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: async def default_data(self) -> JsonData:
@ -198,7 +198,7 @@ class History(JSONConfigFile):
@property @property
def path(self) -> Path: 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: async def default_data(self) -> JsonData:
@ -209,7 +209,7 @@ class History(JSONConfigFile):
class Theme(ConfigFile): class Theme(ConfigFile):
@property @property
def path(self) -> Path: 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 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}", 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) store.mkdir(parents=True, exist_ok=True)
super().__init__( super().__init__(

View File

@ -1,56 +1,37 @@
import asyncio import asyncio
import logging as log import logging as log
import signal import signal
import sys
import traceback import traceback
from concurrent.futures import Future from concurrent.futures import Future
from operator import attrgetter from operator import attrgetter
from threading import Thread from threading import Thread
from typing import Coroutine, Sequence from typing import Coroutine, Sequence
import nio from .backend import Backend
from appdirs import AppDirs
from . import __about__
from .pyotherside_events import CoroutineDone 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: try:
import uvloop import uvloop
except ModuleNotFoundError: except ModuleNotFoundError:
UVLOOP = False log.warning("uvloop module not found, using slower default asyncio loop")
log.info("uvloop not available, using default asyncio loop.")
else: else:
UVLOOP = True uvloop.install()
log.info("uvloop is available.")
class App: class QmlBridge:
def __init__(self) -> None: def __init__(self) -> None:
self.appdirs = AppDirs(appname=__about__.__pkg_name__, roaming=True) self.backend = Backend()
from .backend import Backend self.loop = asyncio.get_event_loop()
self.backend = Backend(app=self) Thread(target=self._start_loop_in_thread).start()
self.debug = False
self.loop = asyncio.get_event_loop()
self.loop_thread = Thread(target=self._loop_starter)
self.loop_thread.start()
def _loop_starter(self) -> None: def _start_loop_in_thread(self) -> None:
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
if UVLOOP:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
self.loop.run_forever() 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) return asyncio.run_coroutine_threadsafe(coro, self.loop)
@ -66,37 +47,40 @@ class App:
CoroutineDone(uuid, result, exception, trace) CoroutineDone(uuid, result, exception, trace)
future = self.run_in_loop(coro) future = self._run_coro_in_loop(coro)
future.add_done_callback(on_done) future.add_done_callback(on_done)
return future return future
def call_backend_coro(self, name: str, uuid: str, args: Sequence[str] = (), def call_backend_coro(
) -> Future: self, name: str, uuid: str, args: Sequence[str] = (),
) -> Future:
return self._call_coro(attrgetter(name)(self.backend)(*args), uuid) return self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
def call_client_coro(self, def call_client_coro(
account_id: str, self, user_id: str, name: str, uuid: str, args: Sequence[str] = (),
name: str, ) -> Future:
uuid: str,
args: Sequence[str] = ()) -> Future: client = self.backend.clients[user_id]
client = self.backend.clients[account_id]
return self._call_coro(attrgetter(name)(client)(*args), uuid) return self._call_coro(attrgetter(name)(client)(*args), uuid)
def pdb(self, additional_data: Sequence = ()) -> None: def pdb(self, additional_data: Sequence = ()) -> None:
ad = additional_data # noqa ad = additional_data # noqa
rl = self.run_in_loop # noqa rc = self._run_coro_in_loop # noqa
ba = self.backend # noqa ba = self.backend # noqa
mo = self.backend.models # noqa mo = self.backend.models # noqa
cl = self.backend.clients 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 from .models.items import Account, Room, Member, Event, Device # noqa
p = print # pdb's `p` doesn't print a class's __str__ # 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 " log.info("\n=> Run `socat readline tcp:127.0.0.1:4444` in a terminal "
"to connect to pdb.") "to connect to pdb.")
@ -107,4 +91,4 @@ class App:
# Make CTRL-C work again # Make CTRL-C work again
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)
APP = App() BRIDGE = QmlBridge()

View File

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

View File

@ -12,7 +12,7 @@ Python {
addImportPath("src") addImportPath("src")
addImportPath("qrc:/src") addImportPath("qrc:/src")
importNames("backend", ["APP"], () => { importNames("backend", ["BRIDGE"], () => {
loadSettings(() => { loadSettings(() => {
callCoro("saved_accounts.any_saved", [], any => { callCoro("saved_accounts.any_saved", [], any => {
if (any) { py.callCoro("load_saved_accounts", []) } if (any) { py.callCoro("load_saved_accounts", []) }
@ -45,7 +45,7 @@ Python {
function callSync(name, args=[]) { 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() 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 future.privates.pythonFuture = pyFuture
}) })
@ -76,7 +76,7 @@ Python {
let call_args = [accountId, name, uuid, args] 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 future.privates.pythonFuture = pyFuture
}) })
}) })