Mercurial > libervia-backend
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## |