comparison libervia/backend/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py@524856bd7b19
children 26b7ed2817da
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # SàT communication bridge
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
5
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
15
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import asyncio
20 from logging import getLogger
21 from functools import partial
22 from pathlib import Path
23 from twisted.spread import pb
24 from twisted.internet import reactor, defer
25 from twisted.internet.error import ConnectionRefusedError, ConnectError
26 from libervia.backend.core import exceptions
27 from libervia.backend.tools import config
28 from sat_frontends.bridge.bridge_frontend import BridgeException
29
30 log = getLogger(__name__)
31
32
33 class SignalsHandler(pb.Referenceable):
34 def __getattr__(self, name):
35 if name.startswith("remote_"):
36 log.debug("calling an unregistered signal: {name}".format(name=name[7:]))
37 return lambda *args, **kwargs: None
38
39 else:
40 raise AttributeError(name)
41
42 def register_signal(self, name, handler, iface="core"):
43 log.debug("registering signal {name}".format(name=name))
44 method_name = "remote_" + name
45 try:
46 self.__getattribute__(method_name)
47 except AttributeError:
48 pass
49 else:
50 raise exceptions.InternalError(
51 "{name} signal handler has been registered twice".format(
52 name=method_name
53 )
54 )
55 setattr(self, method_name, handler)
56
57
58 class bridge(object):
59
60 def __init__(self):
61 self.signals_handler = SignalsHandler()
62
63 def __getattr__(self, name):
64 return partial(self.call, name)
65
66 def _generic_errback(self, err):
67 log.error(f"bridge error: {err}")
68
69 def _errback(self, failure_, ori_errback):
70 """Convert Failure to BridgeException"""
71 ori_errback(
72 BridgeException(
73 name=failure_.type.decode('utf-8'),
74 message=str(failure_.value)
75 )
76 )
77
78 def remote_callback(self, result, callback):
79 """call callback with argument or None
80
81 if result is not None not argument is used,
82 else result is used as argument
83 @param result: remote call result
84 @param callback(callable): method to call on result
85 """
86 if result is None:
87 callback()
88 else:
89 callback(result)
90
91 def call(self, name, *args, **kwargs):
92 """call a remote method
93
94 @param name(str): name of the bridge method
95 @param args(list): arguments
96 may contain callback and errback as last 2 items
97 @param kwargs(dict): keyword arguments
98 may contain callback and errback
99 """
100 callback = errback = None
101 if kwargs:
102 try:
103 callback = kwargs.pop("callback")
104 except KeyError:
105 pass
106 try:
107 errback = kwargs.pop("errback")
108 except KeyError:
109 pass
110 elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
111 errback = args.pop()
112 callback = args.pop()
113 d = self.root.callRemote(name, *args, **kwargs)
114 if callback is not None:
115 d.addCallback(self.remote_callback, callback)
116 if errback is not None:
117 d.addErrback(errback)
118
119 def _init_bridge_eb(self, failure_):
120 log.error("Can't init bridge: {msg}".format(msg=failure_))
121 return failure_
122
123 def _set_root(self, root):
124 """set remote root object
125
126 bridge will then be initialised
127 """
128 self.root = root
129 d = root.callRemote("initBridge", self.signals_handler)
130 d.addErrback(self._init_bridge_eb)
131 return d
132
133 def get_root_object_eb(self, failure_):
134 """Call errback with appropriate bridge error"""
135 if failure_.check(ConnectionRefusedError, ConnectError):
136 raise exceptions.BridgeExceptionNoService
137 else:
138 raise failure_
139
140 def bridge_connect(self, callback, errback):
141 factory = pb.PBClientFactory()
142 conf = config.parse_main_conf()
143 get_conf = partial(config.get_conf, conf, "bridge_pb", "")
144 conn_type = get_conf("connection_type", "unix_socket")
145 if conn_type == "unix_socket":
146 local_dir = Path(config.config_get(conf, "", "local_dir")).resolve()
147 socket_path = local_dir / "bridge_pb"
148 reactor.connectUNIX(str(socket_path), factory)
149 elif conn_type == "socket":
150 host = get_conf("host", "localhost")
151 port = int(get_conf("port", 8789))
152 reactor.connectTCP(host, port, factory)
153 else:
154 raise ValueError(f"Unknown pb connection type: {conn_type!r}")
155 d = factory.getRootObject()
156 d.addCallback(self._set_root)
157 if callback is not None:
158 d.addCallback(lambda __: callback())
159 d.addErrback(self.get_root_object_eb)
160 if errback is not None:
161 d.addErrback(lambda failure_: errback(failure_.value))
162 return d
163
164 def register_signal(self, functionName, handler, iface="core"):
165 self.signals_handler.register_signal(functionName, handler, iface)
166
167
168 ##METHODS_PART##
169
170 class AIOSignalsHandler(SignalsHandler):
171
172 def register_signal(self, name, handler, iface="core"):
173 async_handler = lambda *args, **kwargs: defer.Deferred.fromFuture(
174 asyncio.ensure_future(handler(*args, **kwargs)))
175 return super().register_signal(name, async_handler, iface)
176
177
178 class AIOBridge(bridge):
179
180 def __init__(self):
181 self.signals_handler = AIOSignalsHandler()
182
183 def _errback(self, failure_):
184 """Convert Failure to BridgeException"""
185 raise BridgeException(
186 name=failure_.type.decode('utf-8'),
187 message=str(failure_.value)
188 )
189
190 def call(self, name, *args, **kwargs):
191 d = self.root.callRemote(name, *args, *kwargs)
192 d.addErrback(self._errback)
193 return d.asFuture(asyncio.get_event_loop())
194
195 async def bridge_connect(self):
196 d = super().bridge_connect(callback=None, errback=None)
197 return await d.asFuture(asyncio.get_event_loop())
198
199 ##ASYNC_METHODS_PART##