holado 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holado might be problematic. Click here for more details.
- holado/__init__.py +61 -23
- holado/common/context/service_manager.py +10 -2
- holado/common/context/session_context.py +43 -12
- holado/common/handlers/object.py +12 -4
- holado/holado_config.py +1 -0
- {holado-0.2.3.dist-info → holado-0.2.5.dist-info}/METADATA +1 -1
- {holado-0.2.3.dist-info → holado-0.2.5.dist-info}/RECORD +30 -30
- holado_ais/ais/ais_messages.py +140 -139
- holado_core/common/tools/path_manager.py +8 -4
- holado_grpc/api/rpc/grpc_client.py +122 -118
- holado_helper/docker/logging.conf +3 -1
- holado_helper/docker/run_holado_test_nonreg_in_docker.sh +28 -28
- holado_helper/docker/run_terminal_in_docker-with_docker_control.sh +27 -27
- holado_helper/docker/run_terminal_in_docker.sh +26 -26
- holado_helper/script/initialize_script.py +5 -5
- holado_logging/__init__.py +7 -9
- holado_logging/common/logging/holado_logger.py +2 -2
- holado_logging/common/logging/log_config.py +152 -127
- holado_logging/common/logging/log_manager.py +20 -19
- holado_multitask/multitasking/multitask_manager.py +1 -1
- holado_multitask/multithreading/thread.py +8 -4
- holado_protobuf/__init__.py +1 -1
- holado_protobuf/ipc/protobuf/protobuf_messages.py +821 -818
- holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +1 -2
- holado_test/behave/independant_runner.py +3 -5
- holado_test/scenario/step_tools.py +2 -0
- test_holado/environment.py +1 -1
- test_holado/logging.conf +3 -1
- {holado-0.2.3.dist-info → holado-0.2.5.dist-info}/WHEEL +0 -0
- {holado-0.2.3.dist-info → holado-0.2.5.dist-info}/licenses/LICENSE +0 -0
holado_ais/ais/ais_messages.py
CHANGED
|
@@ -21,7 +21,6 @@ from holado_core.common.tools.string_tools import StrTools
|
|
|
21
21
|
from holado_binary.ipc.binary import Binary
|
|
22
22
|
from holado_core.common.tools.tools import Tools
|
|
23
23
|
from holado_python.standard_library.typing import Typing
|
|
24
|
-
from holado_ais.ais.patch_pyais import HATagBlock, HA_ais_to_nmea_0183
|
|
25
24
|
import copy
|
|
26
25
|
|
|
27
26
|
logger = logging.getLogger(__name__)
|
|
@@ -29,6 +28,7 @@ logger = logging.getLogger(__name__)
|
|
|
29
28
|
try:
|
|
30
29
|
import pyais
|
|
31
30
|
from bitarray import bitarray
|
|
31
|
+
from holado_ais.ais.patch_pyais import HATagBlock, HA_ais_to_nmea_0183
|
|
32
32
|
with_pyais = True
|
|
33
33
|
|
|
34
34
|
# Patch pyais with some fixes
|
|
@@ -36,7 +36,6 @@ try:
|
|
|
36
36
|
except Exception as exc:
|
|
37
37
|
if Tools.do_log(logger, logging.DEBUG):
|
|
38
38
|
logger.debug(f"AIS is not available. Initialization failed on error: {exc}")
|
|
39
|
-
logger.warning(f"AIS is not available. Initialization failed on error: {exc}")
|
|
40
39
|
with_pyais = False
|
|
41
40
|
|
|
42
41
|
|
|
@@ -47,157 +46,159 @@ class AISMessages(object):
|
|
|
47
46
|
def is_available(cls):
|
|
48
47
|
return with_pyais
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
self.__tag_block_group_id = 0
|
|
52
|
-
|
|
53
|
-
def default_message_data(self, msg_type:int):
|
|
54
|
-
"""Return default data values for required data by pyais but not functionnaly required.
|
|
55
|
-
It is useful when generating custom messages, for example for tests.
|
|
56
|
-
"""
|
|
57
|
-
return {}
|
|
58
|
-
|
|
59
|
-
def new_message_as_bitarray_bytes(self, msg_type:int, data:Union[dict,TableWithHeader]):
|
|
60
|
-
msg = self.new_message(msg_type, data)
|
|
61
|
-
res = self.convert_message_to_bytes(msg)
|
|
62
|
-
return res
|
|
63
|
-
|
|
64
|
-
def new_message(self, msg_type:int, data:Union[dict,TableWithHeader]):
|
|
65
|
-
if isinstance(data, TableWithHeader):
|
|
66
|
-
data = self.__convert_table_to_dict(data)
|
|
67
|
-
if not isinstance(data, dict):
|
|
68
|
-
raise TechnicalException(f"Unexpected data type '{Typing.get_object_class_fullname(data)}'")
|
|
49
|
+
if with_pyais:
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return res
|
|
73
|
-
|
|
74
|
-
def decode(self, *args, encoded_msg=None, parts_separator=b'\n'):
|
|
75
|
-
if encoded_msg is not None:
|
|
76
|
-
if isinstance(encoded_msg, str):
|
|
77
|
-
encoded_bytes = encoded_msg.encode("utf-8")
|
|
78
|
-
elif isinstance(encoded_msg, bytes):
|
|
79
|
-
encoded_bytes = encoded_msg
|
|
80
|
-
else:
|
|
81
|
-
raise TechnicalException(f"Parameter 'encoded_msg' must be of type str or bytes")
|
|
82
|
-
args = encoded_bytes.split(parts_separator)
|
|
83
|
-
return pyais.decode(*args, error_if_checksum_invalid=True)
|
|
84
|
-
|
|
85
|
-
def decode_message(self, msg_type:int, bit_array:Union[str,bitarray]):
|
|
86
|
-
if isinstance(bit_array, str):
|
|
87
|
-
if StrTools.is_hex(bit_array) and not StrTools.is_bitarray(bit_array):
|
|
88
|
-
bit_array = Binary.convert_hex_str_to_bin_str(bit_array)
|
|
89
|
-
bit_array = bitarray(bit_array)
|
|
90
|
-
if not isinstance(bit_array, bitarray):
|
|
91
|
-
raise TechnicalException(f"Bad parameter 'bit_array' (accepted types: bitarray, str)")
|
|
92
|
-
|
|
93
|
-
message_type = self.__get_message_type(msg_type)
|
|
94
|
-
return message_type.from_bitarray(bit_array)
|
|
95
|
-
|
|
96
|
-
def encode_raw_payload(self, payload_hex: str, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
97
|
-
from pyais.util import encode_ascii_6
|
|
51
|
+
def __init__(self):
|
|
52
|
+
self.__tag_block_group_id = 0
|
|
98
53
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
54
|
+
def default_message_data(self, msg_type:int):
|
|
55
|
+
"""Return default data values for required data by pyais but not functionnaly required.
|
|
56
|
+
It is useful when generating custom messages, for example for tests.
|
|
57
|
+
"""
|
|
58
|
+
return {}
|
|
102
59
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
60
|
+
def new_message_as_bitarray_bytes(self, msg_type:int, data:Union[dict,TableWithHeader]):
|
|
61
|
+
msg = self.new_message(msg_type, data)
|
|
62
|
+
res = self.convert_message_to_bytes(msg)
|
|
63
|
+
return res
|
|
106
64
|
|
|
107
|
-
|
|
108
|
-
|
|
65
|
+
def new_message(self, msg_type:int, data:Union[dict,TableWithHeader]):
|
|
66
|
+
if isinstance(data, TableWithHeader):
|
|
67
|
+
data = self.__convert_table_to_dict(data)
|
|
68
|
+
if not isinstance(data, dict):
|
|
69
|
+
raise TechnicalException(f"Unexpected data type '{Typing.get_object_class_fullname(data)}'")
|
|
70
|
+
|
|
71
|
+
message_type = self.__get_message_type(msg_type)
|
|
72
|
+
res = message_type.create(**data)
|
|
73
|
+
return res
|
|
74
|
+
|
|
75
|
+
def decode(self, *args, encoded_msg=None, parts_separator=b'\n'):
|
|
76
|
+
if encoded_msg is not None:
|
|
77
|
+
if isinstance(encoded_msg, str):
|
|
78
|
+
encoded_bytes = encoded_msg.encode("utf-8")
|
|
79
|
+
elif isinstance(encoded_msg, bytes):
|
|
80
|
+
encoded_bytes = encoded_msg
|
|
81
|
+
else:
|
|
82
|
+
raise TechnicalException(f"Parameter 'encoded_msg' must be of type str or bytes")
|
|
83
|
+
args = encoded_bytes.split(parts_separator)
|
|
84
|
+
return pyais.decode(*args, error_if_checksum_invalid=True)
|
|
109
85
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def encode_msg(self, msg, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
121
|
-
if not isinstance(msg, pyais.messages.Payload):
|
|
122
|
-
raise TechnicalException(f"Parameter 'msg' is not an AIS message (obtained type: {Typing.get_object_class_fullname(msg)})")
|
|
86
|
+
def decode_message(self, msg_type:int, bit_array:Union[str,bitarray]):
|
|
87
|
+
if isinstance(bit_array, str):
|
|
88
|
+
if StrTools.is_hex(bit_array) and not StrTools.is_bitarray(bit_array):
|
|
89
|
+
bit_array = Binary.convert_hex_str_to_bin_str(bit_array)
|
|
90
|
+
bit_array = bitarray(bit_array)
|
|
91
|
+
if not isinstance(bit_array, bitarray):
|
|
92
|
+
raise TechnicalException(f"Bad parameter 'bit_array' (accepted types: bitarray, str)")
|
|
93
|
+
|
|
94
|
+
message_type = self.__get_message_type(msg_type)
|
|
95
|
+
return message_type.from_bitarray(bit_array)
|
|
123
96
|
|
|
124
|
-
|
|
125
|
-
|
|
97
|
+
def encode_raw_payload(self, payload_hex: str, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
98
|
+
from pyais.util import encode_ascii_6
|
|
99
|
+
|
|
100
|
+
payload_bitarray = bitarray()
|
|
101
|
+
payload_bitarray.frombytes(bytes.fromhex(payload_hex))
|
|
102
|
+
payload_ascii6, fill_bits = encode_ascii_6(payload_bitarray)
|
|
103
|
+
|
|
104
|
+
seq_id = kwargs.pop('seq_id') if 'seq_id' in kwargs else self.__tag_block_group_id % 10
|
|
105
|
+
radio_channel = kwargs['radio_channel'] if 'radio_channel' in kwargs else 'A'
|
|
106
|
+
group_id = kwargs.pop('group_id') if 'group_id' in kwargs else None
|
|
107
|
+
|
|
108
|
+
sentences = HA_ais_to_nmea_0183(payload_ascii6, talker_id, radio_channel, fill_bits, seq_id)
|
|
109
|
+
return self.__encode_add_tag_block(sentences, tag_block, with_tag_block=with_tag_block, group_id=group_id)
|
|
110
|
+
|
|
111
|
+
def encode(self, data, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
112
|
+
if isinstance(data, pyais.messages.Payload):
|
|
113
|
+
return self.encode_msg(data, tag_block=tag_block, with_tag_block=with_tag_block, talker_id=talker_id, **kwargs)
|
|
114
|
+
elif isinstance(data, dict):
|
|
115
|
+
return self.encode_data(data, tag_block=tag_block, with_tag_block=with_tag_block, talker_id=talker_id, **kwargs)
|
|
116
|
+
elif isinstance(data, TableWithHeader):
|
|
117
|
+
return self.encode_table(data, tag_block=tag_block, with_tag_block=with_tag_block, talker_id=talker_id, **kwargs)
|
|
118
|
+
else:
|
|
119
|
+
raise TechnicalException(f"Unexpected data type '{Typing.get_object_class_fullname(data)}'")
|
|
126
120
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
121
|
+
def encode_msg(self, msg, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
122
|
+
if not isinstance(msg, pyais.messages.Payload):
|
|
123
|
+
raise TechnicalException(f"Parameter 'msg' is not an AIS message (obtained type: {Typing.get_object_class_fullname(msg)})")
|
|
124
|
+
|
|
125
|
+
seq_id = kwargs.pop('seq_id') if 'seq_id' in kwargs else self.__tag_block_group_id % 10
|
|
126
|
+
group_id = kwargs.pop('group_id') if 'group_id' in kwargs else None
|
|
127
|
+
|
|
128
|
+
sentences = pyais.encode_msg(msg, seq_id=seq_id, talker_id=talker_id, **kwargs)
|
|
129
|
+
return self.__encode_add_tag_block(sentences, tag_block, with_tag_block=with_tag_block, group_id=group_id)
|
|
133
130
|
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
def encode_data(self, data, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
132
|
+
if not isinstance(data, dict):
|
|
133
|
+
raise TechnicalException(f"Parameter 'data' is not a dict (obtained type: {Typing.get_object_class_fullname(data)})")
|
|
134
|
+
|
|
135
|
+
seq_id = kwargs.pop('seq_id') if 'seq_id' in kwargs else self.__tag_block_group_id % 10
|
|
136
|
+
group_id = kwargs.pop('group_id') if 'group_id' in kwargs else None
|
|
137
|
+
|
|
138
|
+
sentences = pyais.encode_dict(data, seq_id=seq_id, talker_id=talker_id, **kwargs)
|
|
139
|
+
return self.__encode_add_tag_block(sentences, tag_block, with_tag_block=with_tag_block, group_id=group_id)
|
|
136
140
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def encode_table(self, table, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
141
|
-
data = self.__convert_table_to_dict(table)
|
|
142
|
-
return self.encode_data(data, tag_block=tag_block, with_tag_block=with_tag_block, talker_id=talker_id, **kwargs)
|
|
143
|
-
|
|
144
|
-
def __encode_add_tag_block(self, encoded_sentences, tag_block: HATagBlock = None, with_tag_block=True, group_id=None):
|
|
145
|
-
from pyais.messages import TagBlockGroup
|
|
141
|
+
def encode_table(self, table, tag_block: HATagBlock = None, with_tag_block=True, talker_id: str = "AIVDM", **kwargs):
|
|
142
|
+
data = self.__convert_table_to_dict(table)
|
|
143
|
+
return self.encode_data(data, tag_block=tag_block, with_tag_block=with_tag_block, talker_id=talker_id, **kwargs)
|
|
146
144
|
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
def __encode_add_tag_block(self, encoded_sentences, tag_block: HATagBlock = None, with_tag_block=True, group_id=None):
|
|
146
|
+
from pyais.messages import TagBlockGroup
|
|
147
|
+
|
|
148
|
+
if not with_tag_block:
|
|
149
|
+
return encoded_sentences
|
|
150
|
+
|
|
151
|
+
res = []
|
|
152
|
+
nb_sentences = len(encoded_sentences)
|
|
153
|
+
if nb_sentences > 1 and group_id is None:
|
|
154
|
+
group_id = self.__tag_block_group_id
|
|
155
|
+
# Increment group_id for next message needing a group_id
|
|
156
|
+
self.__new_tag_block_group_id()
|
|
157
|
+
elif nb_sentences == 1:
|
|
158
|
+
# No group tag block is needed
|
|
159
|
+
group_id = None
|
|
160
|
+
|
|
161
|
+
for index, sentence in enumerate(encoded_sentences):
|
|
162
|
+
sentence_tag_block = copy.deepcopy(tag_block) if index == 0 else None
|
|
163
|
+
if group_id is not None:
|
|
164
|
+
if sentence_tag_block is None:
|
|
165
|
+
sentence_tag_block = HATagBlock()
|
|
166
|
+
sentence_tag_block._group = TagBlockGroup(msg_id=index+1, total=nb_sentences, group_id=group_id)
|
|
167
|
+
|
|
168
|
+
if sentence_tag_block is not None:
|
|
169
|
+
res.append('\\' + sentence_tag_block.to_raw().decode() + '\\' + sentence)
|
|
170
|
+
else:
|
|
171
|
+
res.append(sentence)
|
|
172
|
+
|
|
173
|
+
return res
|
|
149
174
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
self.__new_tag_block_group_id()
|
|
156
|
-
elif nb_sentences == 1:
|
|
157
|
-
# No group tag block is needed
|
|
158
|
-
group_id = None
|
|
175
|
+
def __new_tag_block_group_id(self):
|
|
176
|
+
self.__tag_block_group_id += 1
|
|
177
|
+
if self.__tag_block_group_id > 9999:
|
|
178
|
+
self.__tag_block_group_id = 0
|
|
179
|
+
return self.__tag_block_group_id
|
|
159
180
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if group_id is not None:
|
|
163
|
-
if sentence_tag_block is None:
|
|
164
|
-
sentence_tag_block = HATagBlock()
|
|
165
|
-
sentence_tag_block._group = TagBlockGroup(msg_id=index+1, total=nb_sentences, group_id=group_id)
|
|
166
|
-
|
|
167
|
-
if sentence_tag_block is not None:
|
|
168
|
-
res.append('\\' + sentence_tag_block.to_raw().decode() + '\\' + sentence)
|
|
169
|
-
else:
|
|
170
|
-
res.append(sentence)
|
|
181
|
+
def convert_message_to_binary_str(self, msg):
|
|
182
|
+
return msg.to_bitarray().to01()
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def __new_tag_block_group_id(self):
|
|
175
|
-
self.__tag_block_group_id += 1
|
|
176
|
-
if self.__tag_block_group_id > 9999:
|
|
177
|
-
self.__tag_block_group_id = 0
|
|
178
|
-
return self.__tag_block_group_id
|
|
179
|
-
|
|
180
|
-
def convert_message_to_binary_str(self, msg):
|
|
181
|
-
return msg.to_bitarray().to01()
|
|
182
|
-
|
|
183
|
-
def convert_message_to_bytes(self, msg):
|
|
184
|
-
return msg.to_bitarray().tobytes()
|
|
185
|
-
|
|
186
|
-
def __get_message_type(self, msg_type:int):
|
|
187
|
-
import importlib
|
|
188
|
-
module = importlib.import_module('pyais.messages')
|
|
189
|
-
res = getattr(module, f"MessageType{msg_type}")
|
|
190
|
-
return res
|
|
184
|
+
def convert_message_to_bytes(self, msg):
|
|
185
|
+
return msg.to_bitarray().tobytes()
|
|
191
186
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
def __get_message_type(self, msg_type:int):
|
|
188
|
+
import importlib
|
|
189
|
+
module = importlib.import_module('pyais.messages')
|
|
190
|
+
res = getattr(module, f"MessageType{msg_type}")
|
|
191
|
+
return res
|
|
192
|
+
|
|
193
|
+
def __convert_table_to_dict(self, table):
|
|
194
|
+
if not ValueTableManager.is_table_with_header(table):
|
|
195
|
+
raise TechnicalException(f"Parameter 'table' is not a table with header (obtained type: {Typing.get_object_class_fullname(table)})")
|
|
196
|
+
|
|
197
|
+
if ValueTableManager.verify_table_is_name_value_table(table, raise_exception=False):
|
|
198
|
+
res = ValueTableConverter.convert_name_value_table_2_dict(table)
|
|
199
|
+
else:
|
|
200
|
+
res = ValueTableConverter.convert_table_with_header_to_dict(table)
|
|
201
|
+
return res
|
|
195
202
|
|
|
196
|
-
if ValueTableManager.verify_table_is_name_value_table(table, raise_exception=False):
|
|
197
|
-
res = ValueTableConverter.convert_name_value_table_2_dict(table)
|
|
198
|
-
else:
|
|
199
|
-
res = ValueTableConverter.convert_table_with_header_to_dict(table)
|
|
200
|
-
return res
|
|
201
|
-
|
|
202
203
|
|
|
203
204
|
|
|
@@ -122,10 +122,14 @@ class PathManager(object):
|
|
|
122
122
|
else:
|
|
123
123
|
os.replace(src, dst)
|
|
124
124
|
|
|
125
|
+
def get_user_home_path(self):
|
|
126
|
+
import pathlib
|
|
127
|
+
return str(pathlib.Path.home())
|
|
128
|
+
|
|
125
129
|
def get_local_resources_path(self, name=None):
|
|
126
|
-
base_path = os.getenv('
|
|
130
|
+
base_path = os.getenv('HOLADO_LOCAL_RESOURCES_BASEDIR')
|
|
127
131
|
if base_path is None:
|
|
128
|
-
|
|
132
|
+
base_path = os.path.join(self.get_user_home_path(), '.holado', 'resources')
|
|
129
133
|
|
|
130
134
|
if name is not None:
|
|
131
135
|
return os.path.join(base_path, name)
|
|
@@ -133,9 +137,9 @@ class PathManager(object):
|
|
|
133
137
|
return base_path
|
|
134
138
|
|
|
135
139
|
def get_reports_path(self, name=None):
|
|
136
|
-
base_path = os.getenv('
|
|
140
|
+
base_path = os.getenv('HOLADO_OUTPUT_BASEDIR')
|
|
137
141
|
if base_path is None:
|
|
138
|
-
|
|
142
|
+
base_path = os.path.join(self.get_user_home_path(), '.holado', 'output')
|
|
139
143
|
|
|
140
144
|
if name is not None:
|
|
141
145
|
return os.path.join(base_path, "reports", name)
|
|
@@ -47,135 +47,139 @@ class GRpcClient(object):
|
|
|
47
47
|
def is_available(cls):
|
|
48
48
|
return with_grpc_requests
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
self
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def initialize(self, func_grpc_services, func_protobuf_converter, func_protobuf_messages):
|
|
61
|
-
self.__func_grpc_services = func_grpc_services
|
|
62
|
-
self.__func_protobuf_converter = func_protobuf_converter
|
|
63
|
-
self.__func_protobuf_messages = func_protobuf_messages
|
|
50
|
+
if with_grpc_requests:
|
|
51
|
+
def __init__(self, name, endpoint, **kwargs):
|
|
52
|
+
self.__name = name
|
|
53
|
+
self.__endpoint = endpoint
|
|
54
|
+
self.__kwargs = kwargs
|
|
55
|
+
self.__client = grpc_requests.client.get_by_endpoint(endpoint, **kwargs)
|
|
56
|
+
|
|
57
|
+
self.__func_grpc_services = None
|
|
58
|
+
self.__func_protobuf_converter = None
|
|
59
|
+
self.__func_protobuf_messages = None
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def __protobuf_messages(self):
|
|
75
|
-
return self.__func_protobuf_messages()
|
|
76
|
-
|
|
77
|
-
@property
|
|
78
|
-
def name(self):
|
|
79
|
-
return self.__name
|
|
61
|
+
def initialize(self, func_grpc_services, func_protobuf_converter, func_protobuf_messages):
|
|
62
|
+
self.__func_grpc_services = func_grpc_services
|
|
63
|
+
self.__func_protobuf_converter = func_protobuf_converter
|
|
64
|
+
self.__func_protobuf_messages = func_protobuf_messages
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def __grpc_services(self):
|
|
68
|
+
return self.__func_grpc_services()
|
|
80
69
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
@property
|
|
71
|
+
def __protobuf_converter(self):
|
|
72
|
+
return self.__func_protobuf_converter()
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:param raw_output: if method should return a proto object or a json oject (default: False)
|
|
89
|
-
:param kwargs: other arguments for underlying grpc_requests method, or further underlying grpc method (ex: 'timeout')
|
|
90
|
-
:returns: if raw_output==True, returns a proto object, else returns a json object with proto data
|
|
91
|
-
"""
|
|
92
|
-
logger.info(f"Requesting {service}.{method} with data [{request}] (raw_output: {raw_output} ; kwargs:{kwargs})")
|
|
74
|
+
@property
|
|
75
|
+
def __protobuf_messages(self):
|
|
76
|
+
return self.__func_protobuf_messages()
|
|
93
77
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
kwargs = {}
|
|
98
|
-
if 'timeout' not in kwargs:
|
|
99
|
-
kwargs['timeout'] = Config.join_timeout_seconds
|
|
100
|
-
raise_on_timeout = True
|
|
101
|
-
timeout = kwargs['timeout'] if 'timeout' in kwargs else None
|
|
78
|
+
@property
|
|
79
|
+
def name(self):
|
|
80
|
+
return self.__name
|
|
102
81
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
82
|
+
@property
|
|
83
|
+
def internal_client(self) -> grpc_requests.client.Client:
|
|
84
|
+
return self.__client
|
|
85
|
+
|
|
86
|
+
def request(self, service, method, request, raw_output=False, **kwargs):
|
|
87
|
+
"""
|
|
88
|
+
:param request: request data in json or proto format
|
|
89
|
+
:param raw_output: if method should return a proto object or a json oject (default: False)
|
|
90
|
+
:param kwargs: other arguments for underlying grpc_requests method, or further underlying grpc method (ex: 'timeout')
|
|
91
|
+
:returns: if raw_output==True, returns a proto object, else returns a json object with proto data
|
|
92
|
+
"""
|
|
93
|
+
logger.info(f"Requesting {service}.{method} with data [{request}] (raw_output: {raw_output} ; kwargs:{kwargs})")
|
|
94
|
+
|
|
95
|
+
# Set a default timeout
|
|
96
|
+
raise_on_timeout = False
|
|
97
|
+
if kwargs is None:
|
|
98
|
+
kwargs = {}
|
|
99
|
+
if 'timeout' not in kwargs:
|
|
100
|
+
kwargs['timeout'] = Config.join_timeout_seconds
|
|
101
|
+
raise_on_timeout = True
|
|
102
|
+
timeout = kwargs['timeout'] if 'timeout' in kwargs else None
|
|
103
|
+
|
|
104
|
+
success = False
|
|
105
|
+
last_exc = None
|
|
106
|
+
for try_nb in range(1,4):
|
|
107
|
+
try:
|
|
108
|
+
# Ask always raw_output=True, so that we get a Protobuf instance, and then conversion is done if needed
|
|
109
|
+
res_proto = self.internal_client.request(service, method, request, raw_output=True, **kwargs)
|
|
110
|
+
except Exception as exc:
|
|
111
|
+
last_exc = exc
|
|
112
|
+
exc_str = str(exc)
|
|
113
|
+
msg_list = [
|
|
114
|
+
f"Request failed (try {try_nb}):",
|
|
115
|
+
f" method: {service}.{method}",
|
|
116
|
+
f" data:",
|
|
117
|
+
Tools.indent_string(8, str(request)),
|
|
118
|
+
f" raw_output: {raw_output}",
|
|
119
|
+
f" kwargs: {kwargs}",
|
|
120
|
+
f" error:",
|
|
121
|
+
Tools.indent_string(8, exc_str) ]
|
|
122
|
+
exc_msg = "\n".join(msg_list)
|
|
123
|
+
if "status = StatusCode.UNAVAILABLE" in exc_str:
|
|
124
|
+
logger.warning("Service temporarily unavailable:\n" + exc_msg)
|
|
131
125
|
time.sleep(1)
|
|
132
126
|
continue
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
elif "status = StatusCode.DEADLINE_EXCEEDED" in exc_str:
|
|
128
|
+
if raise_on_timeout:
|
|
129
|
+
raise TimeoutTechnicalException(exc_msg)
|
|
130
|
+
else:
|
|
131
|
+
logger.warning(f"Timeout ({timeout} s) occured while requesting:\n" + exc_msg)
|
|
132
|
+
time.sleep(1)
|
|
133
|
+
continue
|
|
134
|
+
elif "status = " in exc_str:
|
|
135
|
+
raise FunctionalException(exc_msg) from exc
|
|
136
|
+
else:
|
|
137
|
+
raise TechnicalException(exc_msg) from exc
|
|
135
138
|
else:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
success = True
|
|
139
|
-
break
|
|
140
|
-
|
|
141
|
-
if not success:
|
|
142
|
-
if "status = " in exc_msg:
|
|
143
|
-
raise FunctionalException(exc_msg) from last_exc
|
|
144
|
-
else:
|
|
145
|
-
raise TechnicalException(exc_msg) from last_exc
|
|
139
|
+
success = True
|
|
140
|
+
break
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
if not success:
|
|
143
|
+
if "status = " in exc_msg:
|
|
144
|
+
raise FunctionalException(exc_msg) from last_exc
|
|
145
|
+
else:
|
|
146
|
+
raise TechnicalException(exc_msg) from last_exc
|
|
147
|
+
|
|
148
|
+
# Manage result conversion if needed
|
|
149
|
+
# Note: this step is done manually since grpc_requests has some limitations when raw_output=False:
|
|
150
|
+
# - Field with default values are not set in json result
|
|
151
|
+
# - Some field types are badly managed (ex: uint64 fields appear as string in json)
|
|
152
|
+
if raw_output == True:
|
|
153
|
+
if isinstance(res_proto, grpc._channel._MultiThreadedRendezvous):
|
|
154
|
+
return list(res_proto)
|
|
155
|
+
else:
|
|
156
|
+
return res_proto
|
|
154
157
|
else:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
if isinstance(res_proto, grpc._channel._MultiThreadedRendezvous):
|
|
159
|
+
res = [self.__protobuf_converter.convert_protobuf_object_to_json_object(cur_res) for cur_res in res_proto]
|
|
160
|
+
else:
|
|
161
|
+
res = self.__protobuf_converter.convert_protobuf_object_to_json_object(res_proto)
|
|
162
|
+
return res
|
|
163
|
+
|
|
164
|
+
def get_request_data_type_fullname(self, service, method):
|
|
165
|
+
method_descriptor = self.__grpc_services.get_method_descriptor(service, method)
|
|
166
|
+
return method_descriptor.input_type.full_name
|
|
167
|
+
|
|
168
|
+
def build_request_data(self, service, method, params_table=None, params_dict=None, as_proto=False):
|
|
169
|
+
if Tools.do_log(logger, logging.TRACE): # @UndefinedVariable
|
|
170
|
+
logger.trace(f"Building request data for service method '{service}.{method}'")
|
|
171
|
+
if as_proto is not None and as_proto:
|
|
172
|
+
method_descriptor = self.__grpc_services.get_method_descriptor(service, method)
|
|
173
|
+
res = self.__protobuf_messages.new_message(method_descriptor.input_type.full_name, fields_table=params_table, fields_dict=params_dict)
|
|
159
174
|
else:
|
|
160
|
-
|
|
161
|
-
|
|
175
|
+
if params_table is not None:
|
|
176
|
+
res = ValueTableConverter.convert_name_value_table_2_json_object(params_table)
|
|
177
|
+
elif params_dict is not None:
|
|
178
|
+
res = params_dict
|
|
179
|
+
else:
|
|
180
|
+
res = {}
|
|
162
181
|
|
|
163
|
-
|
|
164
|
-
method_descriptor = self.__grpc_services.get_method_descriptor(service, method)
|
|
165
|
-
return method_descriptor.input_type.full_name
|
|
182
|
+
return res
|
|
166
183
|
|
|
167
|
-
def build_request_data(self, service, method, params_table=None, params_dict=None, as_proto=False):
|
|
168
|
-
if Tools.do_log(logger, logging.TRACE): # @UndefinedVariable
|
|
169
|
-
logger.trace(f"Building request data for service method '{service}.{method}'")
|
|
170
|
-
if as_proto is not None and as_proto:
|
|
171
|
-
method_descriptor = self.__grpc_services.get_method_descriptor(service, method)
|
|
172
|
-
res = self.__protobuf_messages.new_message(method_descriptor.input_type.full_name, fields_table=params_table, fields_dict=params_dict)
|
|
173
|
-
else:
|
|
174
|
-
if params_table is not None:
|
|
175
|
-
res = ValueTableConverter.convert_name_value_table_2_json_object(params_table)
|
|
176
|
-
elif params_dict is not None:
|
|
177
|
-
res = params_dict
|
|
178
|
-
else:
|
|
179
|
-
res = {}
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
|