bridge (embedded): Update and fix:
Fix embedded bridge and adapt to changes in core code: - Add `EmbeddedBridgeSettings` so it can be used in settings. - Add `AIOBridge` and `TwistedBridge` wrappers. - Fix callbacks, instead of logging them (and breaking the errback chain), they are now raised using `BridgeException` like in other bridges. - Ensure that a singleton `Bridge` is used between backend and frontend. - Init backend on first call to `bridge_connect`. - Add Python 3.14 fix (as it is normally done in Launcher which is not run with with `embedded`, we have to do it there). - Track generated files.
This commit is contained in:
parent
15ad700b22
commit
a722464238
6 changed files with 1639 additions and 16 deletions
|
|
@ -66,9 +66,7 @@ class EmbeddedConstructor(base_constructor.Constructor):
|
|||
d = self._methods_cbs["{name}"]({args_result})
|
||||
if callback is not None:
|
||||
d.addCallback({cb_or_lambda})
|
||||
if errback is None:
|
||||
d.addErrback(lambda failure_: log.error(failure_))
|
||||
else:
|
||||
if errback is not None:
|
||||
d.addErrback(errback)
|
||||
return d
|
||||
""".format(**completion)
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from libervia.backend.bridge.embedded import bridge
|
||||
from libervia.backend.bridge.embedded import bridge, TwistedBridge, AIOBridge
|
||||
|
|
|
|||
|
|
@ -21,11 +21,16 @@ from libervia.backend.core.log import getLogger
|
|||
|
||||
log = getLogger(__name__)
|
||||
from libervia.backend.core import exceptions
|
||||
from libervia.frontends.bridge.bridge_frontend import BridgeException
|
||||
|
||||
import asyncio
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.error import ReactorAlreadyInstalledError
|
||||
|
||||
|
||||
class _Bridge(object):
|
||||
class _Bridge:
|
||||
def __init__(self):
|
||||
log.debug("Init embedded bridge...")
|
||||
log.debug("Init embedded bridge…")
|
||||
self._methods_cbs = {}
|
||||
self._signals_cbs = {"core": {}, "plugin": {}}
|
||||
|
||||
|
|
@ -55,9 +60,7 @@ class _Bridge(object):
|
|||
d = self._methods_cbs[name](*args, **kwargs)
|
||||
if callback is not None:
|
||||
d.addCallback(callback if out_sign else lambda __: callback())
|
||||
if errback is None:
|
||||
d.addErrback(lambda failure_: log.error(failure_))
|
||||
else:
|
||||
if errback is not None:
|
||||
d.addErrback(errback)
|
||||
return d
|
||||
else:
|
||||
|
|
@ -114,12 +117,118 @@ class _Bridge(object):
|
|||
|
||||
##METHODS_PART##
|
||||
|
||||
# we want the same instance for both core and frontend
|
||||
bridge = None
|
||||
# Singleton: shared instance between backend and frontend
|
||||
_bridge_instance = None
|
||||
|
||||
|
||||
def bridge():
|
||||
global bridge
|
||||
if bridge is None:
|
||||
bridge = _Bridge()
|
||||
return bridge
|
||||
"""Return the singleton _Bridge instance, creating it if necessary."""
|
||||
global _bridge_instance
|
||||
if _bridge_instance is None:
|
||||
_bridge_instance = _Bridge()
|
||||
return _bridge_instance
|
||||
|
||||
|
||||
class TwistedBridge:
|
||||
"""Embedded bridge wrapper that returns Twisted Deferreds.
|
||||
|
||||
Starts ``LiberviaBackend`` on first bridge connection.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._connected = False
|
||||
|
||||
async def post_init(self):
|
||||
pass
|
||||
|
||||
def _ensure_reactor(self):
|
||||
"""Ensure asyncioreactor is installed."""
|
||||
from twisted.internet import asyncioreactor
|
||||
|
||||
# Python 3.14+ workaround: ensure an event loop exists before installing reactor
|
||||
try:
|
||||
asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
asyncioreactor.install()
|
||||
except ReactorAlreadyInstalledError:
|
||||
from twisted.internet import reactor
|
||||
|
||||
if not isinstance(reactor, asyncioreactor.AsyncioSelectorReactor):
|
||||
raise exceptions.InternalError(
|
||||
f"Wrong reactor installed: {reactor}"
|
||||
) from None
|
||||
|
||||
def _start_backend(self):
|
||||
"""Start LiberviaBackend and return its initialised Deferred."""
|
||||
from libervia.backend.core.main import LiberviaBackend
|
||||
|
||||
self._ensure_reactor()
|
||||
backend = LiberviaBackend()
|
||||
backend._init()
|
||||
return backend.initialised
|
||||
|
||||
def bridge_connect(self):
|
||||
"""Connect to the embedded backend, starting it if necessary."""
|
||||
if self._connected:
|
||||
return defer.succeed(None)
|
||||
self._connected = True
|
||||
return self._start_backend()
|
||||
|
||||
def register_signal(self, name, handler, iface="core"):
|
||||
"""Register a signal handler on the embedded bridge."""
|
||||
bridge().register_signal(name, handler, iface)
|
||||
|
||||
def _errback(self, failure_):
|
||||
"""Convert Failure to BridgeException."""
|
||||
e = failure_.value
|
||||
if isinstance(e, BridgeException):
|
||||
raise e
|
||||
try:
|
||||
condition = e.condition
|
||||
except AttributeError:
|
||||
condition = ""
|
||||
raise BridgeException(
|
||||
type(e).__module__ + "." + type(e).__name__, str(e), condition
|
||||
) from e
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Delegate method calls, ensuring they return Deferreds."""
|
||||
attr = getattr(bridge(), name)
|
||||
if callable(attr) and not name.startswith("_"):
|
||||
|
||||
def deferred_wrapper(*args, **kwargs):
|
||||
result = attr(*args, **kwargs)
|
||||
if isinstance(result, defer.Deferred):
|
||||
result.addErrback(self._errback)
|
||||
return result
|
||||
return defer.succeed(result)
|
||||
|
||||
return deferred_wrapper
|
||||
return attr
|
||||
|
||||
|
||||
class AIOBridge(TwistedBridge):
|
||||
"""Embedded bridge wrapper that returns asyncio Futures."""
|
||||
|
||||
def bridge_connect(self):
|
||||
"""Connect to the embedded backend, starting it if necessary."""
|
||||
if self._connected:
|
||||
return asyncio.ensure_future(defer.succeed(None))
|
||||
self._connected = True
|
||||
d = self._start_backend()
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Delegate method calls, converting Deferreds to Futures."""
|
||||
attr = super().__getattr__(name)
|
||||
if callable(attr) and not name.startswith("_"):
|
||||
|
||||
def aio_wrapper(*args, **kwargs):
|
||||
return attr(*args, **kwargs).asFuture(asyncio.get_event_loop())
|
||||
|
||||
return aio_wrapper
|
||||
return attr
|
||||
|
|
|
|||
1486
libervia/backend/bridge/embedded.py
Normal file
1486
libervia/backend/bridge/embedded.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -232,8 +232,18 @@ class AutoBridgeSettings(ConfigOptions):
|
|||
type: Literal["auto"] = "auto"
|
||||
|
||||
|
||||
class EmbeddedBridgeSettings(ConfigOptions):
|
||||
"""Embedded Bridge.
|
||||
|
||||
Runs frontend and backend in the same process, backend is used like a library.
|
||||
The backend is started automatically on first bridge connection.
|
||||
"""
|
||||
|
||||
type: Literal["embedded"] = "embedded"
|
||||
|
||||
|
||||
BridgeSettings = Annotated[
|
||||
DBusBridgeSettings | PbBridgeSettings | AutoBridgeSettings,
|
||||
DBusBridgeSettings | PbBridgeSettings | AutoBridgeSettings | EmbeddedBridgeSettings,
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
|
|
|||
20
libervia/frontends/bridge/embedded.py
Normal file
20
libervia/frontends/bridge/embedded.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
# Libervia: an XMPP client
|
||||
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from libervia.backend.bridge.embedded import bridge, TwistedBridge, AIOBridge
|
||||
Loading…
Reference in a new issue