holado 0.2.6__py3-none-any.whl → 0.2.8__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-0.2.6.dist-info → holado-0.2.8.dist-info}/METADATA +4 -1
- {holado-0.2.6.dist-info → holado-0.2.8.dist-info}/RECORD +27 -25
- holado_core/common/resource/persisted_method_to_call_manager.py +16 -3
- holado_core/common/tables/table.py +2 -2
- holado_core/common/tools/string_tools.py +9 -0
- holado_core/common/tools/tools.py +2 -2
- holado_core/tests/behave/steps/common/tables_steps.py +10 -3
- holado_helper/script/action.py +16 -7
- holado_python/standard_library/socket/blocking_socket.py +86 -29
- holado_python/standard_library/socket/echo_server.py +4 -3
- holado_python/standard_library/socket/message_socket.py +59 -20
- holado_python/standard_library/socket/non_blocking_socket.py +58 -60
- holado_python/standard_library/socket/socket.py +149 -22
- holado_python/tests/behave/steps/standard_library/socket_steps.py +15 -3
- holado_python/tests/behave/steps/standard_library/ssl_steps.py +1 -1
- holado_scripting/text/interpreter/functions/function_apply_function.py +60 -0
- holado_scripting/text/interpreter/functions/function_to_string.py +50 -0
- holado_scripting/text/interpreter/text_interpreter.py +4 -0
- holado_test/scenario/step_tools.py +4 -3
- holado_value/common/tables/comparators/table_2_value_table_cell_comparator.py +2 -1
- holado_value/common/tables/value_table_cell.py +5 -0
- holado_value/common/tables/value_table_row.py +0 -1
- holado_value/common/tools/value.py +6 -2
- test_holado/features/NonReg/holado_python/standard_library/socket.feature +62 -0
- test_holado/features/NonReg/holado_python/standard_library/socket_with_ssl.feature +77 -1
- {holado-0.2.6.dist-info → holado-0.2.8.dist-info}/WHEEL +0 -0
- {holado-0.2.6.dist-info → holado-0.2.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -17,8 +17,10 @@ from holado_python.standard_library.socket.socket import SocketClient
|
|
|
17
17
|
import abc
|
|
18
18
|
from holado_multitask.multithreading.loopfunctionthreaded import LoopFunctionThreaded
|
|
19
19
|
import selectors
|
|
20
|
-
import
|
|
21
|
-
import
|
|
20
|
+
import ssl
|
|
21
|
+
import select
|
|
22
|
+
from holado.common.handlers.undefined import undefined_argument
|
|
23
|
+
import time
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger(__name__)
|
|
24
26
|
|
|
@@ -34,92 +36,85 @@ class NonBlockingSocketClient(SocketClient):
|
|
|
34
36
|
"""
|
|
35
37
|
__metaclass__ = abc.ABCMeta
|
|
36
38
|
|
|
37
|
-
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
|
|
39
|
+
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument, do_run_with_recv=True, do_run_with_send=True):
|
|
38
40
|
self.__selector = selectors.DefaultSelector()
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
in_bytes=b"",
|
|
43
|
-
out_bytes=b"",
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# Note: __selector and __data must be defined before, since Socket.__init__ can execute create_ipv4_socket
|
|
47
|
-
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
|
|
48
|
-
|
|
49
|
-
self.__start_thread = None
|
|
42
|
+
# Note: __selector must be defined before, since Socket.__init__ can execute create_ipv4_socket
|
|
43
|
+
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay, do_run_with_recv=do_run_with_recv, do_run_with_send=do_run_with_send)
|
|
50
44
|
|
|
51
45
|
def _delete_object(self):
|
|
52
|
-
self.stop()
|
|
53
46
|
if self.internal_socket:
|
|
47
|
+
# Note: stop must be done before unregistering selector
|
|
48
|
+
self.stop()
|
|
54
49
|
self.__selector.unregister(self.internal_socket)
|
|
55
50
|
|
|
56
51
|
super()._delete_object()
|
|
57
52
|
|
|
58
53
|
def _register_socket(self, sock):
|
|
59
54
|
events = selectors.EVENT_READ | selectors.EVENT_WRITE
|
|
60
|
-
self.__selector.register(sock, events, data=self.
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def _data_lock(self):
|
|
64
|
-
return self.__data_lock
|
|
65
|
-
|
|
66
|
-
@property
|
|
67
|
-
def _data(self):
|
|
68
|
-
return self.__data
|
|
55
|
+
self.__selector.register(sock, events, data=self._data)
|
|
69
56
|
|
|
70
57
|
def start(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
|
|
71
58
|
"""Start client event loop.
|
|
72
59
|
"""
|
|
73
60
|
kwargs = {'read_bufsize':read_bufsize, 'read_kwargs':read_kwargs, 'write_kwargs':write_kwargs}
|
|
74
|
-
|
|
75
|
-
self.
|
|
76
|
-
|
|
77
|
-
def stop(self):
|
|
78
|
-
if self.__start_thread is not None:
|
|
79
|
-
self.__start_thread.interrupt()
|
|
80
|
-
self.__start_thread.join()
|
|
81
|
-
self.__start_thread = None
|
|
61
|
+
thread = LoopFunctionThreaded(self._wait_and_process_events, kwargs=kwargs, register_thread=True, delay_before_run_sec=None)
|
|
62
|
+
self._start_thread(thread)
|
|
82
63
|
|
|
83
64
|
def _wait_and_process_events(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
|
|
65
|
+
has_activity = False
|
|
84
66
|
events = self.__selector.select(timeout=None)
|
|
85
67
|
for key, mask in events:
|
|
86
|
-
self._service_connection(key, mask, read_bufsize=read_bufsize, read_kwargs=read_kwargs, write_kwargs=write_kwargs)
|
|
68
|
+
has_activity |= self._service_connection(key, mask, read_bufsize=read_bufsize, read_kwargs=read_kwargs, write_kwargs=write_kwargs)
|
|
69
|
+
|
|
70
|
+
# Wait before next loop if no data was exchanged
|
|
71
|
+
if not has_activity and self._idle_sleep_delay is not None:
|
|
72
|
+
time.sleep(self._idle_sleep_delay)
|
|
87
73
|
|
|
88
74
|
def _service_connection(self, key, mask, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
|
|
75
|
+
has_activity = False
|
|
89
76
|
read_kwargs = read_kwargs if read_kwargs is not None else {}
|
|
90
77
|
write_kwargs = write_kwargs if write_kwargs is not None else {}
|
|
91
78
|
|
|
92
79
|
sock = key.fileobj
|
|
93
80
|
data = key.data
|
|
94
|
-
if mask & selectors.EVENT_READ:
|
|
95
|
-
|
|
81
|
+
if self.is_run_with_recv and mask & selectors.EVENT_READ:
|
|
82
|
+
if self.is_with_ssl:
|
|
83
|
+
# ssl doesn't suppôrt flags != 0
|
|
84
|
+
flags = 0
|
|
85
|
+
else:
|
|
86
|
+
flags = read_kwargs.get('flags', 0)
|
|
87
|
+
|
|
88
|
+
recv_data = sock.recv(read_bufsize, flags)
|
|
96
89
|
if recv_data:
|
|
97
|
-
|
|
90
|
+
has_activity = True
|
|
91
|
+
with self._data_lock: # data is self._data
|
|
98
92
|
data.in_bytes += recv_data
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
94
|
+
logger.debug(f"[{self.name}] Received [{recv_data}] (type: {type(recv_data)} ; total: {len(data.in_bytes)})")
|
|
95
|
+
|
|
96
|
+
if self.is_run_with_send and mask & selectors.EVENT_WRITE:
|
|
97
|
+
with self._data_lock: # data is self._data
|
|
101
98
|
if data.out_bytes:
|
|
99
|
+
has_activity = True
|
|
102
100
|
sent = sock.send(data.out_bytes)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return res
|
|
110
|
-
|
|
111
|
-
def read(self, bufsize=1024):
|
|
112
|
-
with self.__data_lock:
|
|
113
|
-
if self.__data.in_bytes:
|
|
114
|
-
res = self.__data.in_bytes[:bufsize]
|
|
115
|
-
self.__data.in_bytes = self.__data.in_bytes[bufsize:]
|
|
116
|
-
else:
|
|
117
|
-
res = b''
|
|
118
|
-
return res
|
|
101
|
+
if sent > 0:
|
|
102
|
+
data.out_bytes = data.out_bytes[sent:]
|
|
103
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
104
|
+
logger.debug(f"[{self.name}] Sent {sent} data (remaining to send: {len(data.out_bytes)})")
|
|
105
|
+
|
|
106
|
+
return has_activity
|
|
119
107
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
def do_ssl_handshake(self):
|
|
109
|
+
if self.is_with_ssl:
|
|
110
|
+
while True:
|
|
111
|
+
try:
|
|
112
|
+
self.internal_socket.do_handshake()
|
|
113
|
+
break
|
|
114
|
+
except ssl.SSLWantReadError:
|
|
115
|
+
select.select([self.internal_socket], [], [])
|
|
116
|
+
except ssl.SSLWantWriteError:
|
|
117
|
+
select.select([], [self.internal_socket], [])
|
|
123
118
|
|
|
124
119
|
|
|
125
120
|
class TCPNonBlockingSocketClient(NonBlockingSocketClient):
|
|
@@ -127,8 +122,8 @@ class TCPNonBlockingSocketClient(NonBlockingSocketClient):
|
|
|
127
122
|
TCP socket client.
|
|
128
123
|
"""
|
|
129
124
|
|
|
130
|
-
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
|
|
131
|
-
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
|
|
125
|
+
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument, do_run_with_recv=True, do_run_with_send=True):
|
|
126
|
+
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay, do_run_with_recv=do_run_with_recv, do_run_with_send=do_run_with_send)
|
|
132
127
|
|
|
133
128
|
def create_ipv4_socket(self, host, port, **kwargs):
|
|
134
129
|
ssl_context, kwargs = self._new_ssl_context_if_required(**kwargs)
|
|
@@ -137,8 +132,11 @@ class TCPNonBlockingSocketClient(NonBlockingSocketClient):
|
|
|
137
132
|
sock.setblocking(False)
|
|
138
133
|
|
|
139
134
|
if ssl_context:
|
|
140
|
-
|
|
141
|
-
|
|
135
|
+
do_handshake_on_connect = False
|
|
136
|
+
sock = ssl_context.wrap_socket(sock, server_hostname=host, do_handshake_on_connect=do_handshake_on_connect)
|
|
137
|
+
self._set_internal_socket(sock, is_with_ssl=True, is_ssl_handshake_done_on_connect=do_handshake_on_connect)
|
|
138
|
+
else:
|
|
139
|
+
self._set_internal_socket(sock)
|
|
142
140
|
|
|
143
141
|
# Register socket
|
|
144
142
|
self._register_socket(sock)
|
|
@@ -19,6 +19,10 @@ import abc
|
|
|
19
19
|
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
20
20
|
from holado_core.common.tools.tools import Tools
|
|
21
21
|
from holado_python.standard_library.ssl.ssl import SslManager
|
|
22
|
+
import threading
|
|
23
|
+
import types
|
|
24
|
+
from abc import abstractmethod
|
|
25
|
+
from holado.common.handlers.undefined import undefined_argument
|
|
22
26
|
|
|
23
27
|
logger = logging.getLogger(__name__)
|
|
24
28
|
|
|
@@ -31,7 +35,12 @@ class Socket(DeleteableObject):
|
|
|
31
35
|
"""
|
|
32
36
|
__metaclass__ = abc.ABCMeta
|
|
33
37
|
|
|
34
|
-
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
|
|
38
|
+
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
|
|
39
|
+
"""Socket constructor
|
|
40
|
+
@param name: Socket name
|
|
41
|
+
@param create_ipv4_socket_kwargs: arguments to create an IPv4 socket
|
|
42
|
+
@param idle_sleep_delay: delay to sleep when socket is idle (used in some clients/servers to control CPU resource impact ; default: 0.01 s)
|
|
43
|
+
"""
|
|
35
44
|
if name is None and create_ipv4_socket_kwargs is not None:
|
|
36
45
|
if not set(create_ipv4_socket_kwargs.keys()).issuperset({'host', 'port'}):
|
|
37
46
|
raise FunctionalException(f"Parameters 'host' and 'port' must be defined")
|
|
@@ -39,6 +48,13 @@ class Socket(DeleteableObject):
|
|
|
39
48
|
|
|
40
49
|
super().__init__(name)
|
|
41
50
|
self.__socket = None
|
|
51
|
+
self.__is_started = False
|
|
52
|
+
self.__start_thread = None
|
|
53
|
+
self.__idle_sleep_delay = idle_sleep_delay if idle_sleep_delay is not undefined_argument else 0.01
|
|
54
|
+
|
|
55
|
+
# SSL management
|
|
56
|
+
self.__is_with_ssl = False
|
|
57
|
+
self.__is_ssl_handshake_done = False
|
|
42
58
|
|
|
43
59
|
if create_ipv4_socket_kwargs is not None:
|
|
44
60
|
self.create_ipv4_socket(**create_ipv4_socket_kwargs)
|
|
@@ -56,8 +72,22 @@ class Socket(DeleteableObject):
|
|
|
56
72
|
def internal_socket(self) -> socket.socket:
|
|
57
73
|
return self.__socket
|
|
58
74
|
|
|
59
|
-
|
|
75
|
+
@property
|
|
76
|
+
def is_started(self):
|
|
77
|
+
return self.__is_started
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def is_with_ssl(self):
|
|
81
|
+
return self.__is_with_ssl
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def _idle_sleep_delay(self):
|
|
85
|
+
return self.__idle_sleep_delay
|
|
86
|
+
|
|
87
|
+
def _set_internal_socket(self, socket, is_with_ssl=False, is_ssl_handshake_done_on_connect=True):
|
|
60
88
|
self.__socket = socket
|
|
89
|
+
self.__is_with_ssl = is_with_ssl
|
|
90
|
+
self.__is_ssl_handshake_done = is_ssl_handshake_done_on_connect
|
|
61
91
|
|
|
62
92
|
@abc.abstractmethod
|
|
63
93
|
def create_ipv4_socket(self, host, port, **kwargs):
|
|
@@ -69,14 +99,40 @@ class Socket(DeleteableObject):
|
|
|
69
99
|
self.__socket.shutdown(SHUT_RDWR)
|
|
70
100
|
except OSError as exc:
|
|
71
101
|
if 'Errno 107' in str(exc):
|
|
72
|
-
logger.info(f"Got following error on socket shutdown (known to appear sometimes
|
|
102
|
+
logger.info(f"Got following error on socket shutdown (known to appear sometimes during shutdown): {exc}")
|
|
73
103
|
else:
|
|
74
104
|
raise exc
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
finally:
|
|
106
|
+
self.__socket.close()
|
|
107
|
+
self.__socket = None
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def start(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
|
|
111
|
+
raise NotImplementedError()
|
|
112
|
+
|
|
113
|
+
def _start_thread(self, thread):
|
|
114
|
+
# Start thread
|
|
115
|
+
self.__start_thread = thread
|
|
116
|
+
self.__start_thread.start()
|
|
117
|
+
self.__is_started = True
|
|
118
|
+
|
|
119
|
+
if Tools.do_log(logger, logging.DEBUG):
|
|
120
|
+
logger.debug(f"[{self.name}] Started")
|
|
121
|
+
|
|
122
|
+
def stop(self):
|
|
123
|
+
if self.__start_thread is not None:
|
|
124
|
+
if self.__start_thread.is_interruptable:
|
|
125
|
+
self.__start_thread.interrupt()
|
|
126
|
+
self.__start_thread.join()
|
|
127
|
+
self.__start_thread = None
|
|
128
|
+
self.__is_started = False
|
|
129
|
+
|
|
130
|
+
if Tools.do_log(logger, logging.DEBUG):
|
|
131
|
+
logger.debug(f"[{self.name}] Stopped")
|
|
77
132
|
|
|
78
133
|
def read(self, bufsize=1024, **kwargs):
|
|
79
|
-
|
|
134
|
+
flags = kwargs.get('flags', 0)
|
|
135
|
+
res = self.internal_socket.recv(bufsize, flags)
|
|
80
136
|
if logger.isEnabledFor(logging.DEBUG):
|
|
81
137
|
logger.debug(f"[{self.name}] Received [{res}] (type: {type(res)})")
|
|
82
138
|
return res
|
|
@@ -101,6 +157,16 @@ class Socket(DeleteableObject):
|
|
|
101
157
|
ssl_kwargs = Tools.pop_sub_kwargs(socket_kwargs, 'ssl.')
|
|
102
158
|
res = SslManager.new_ssl_context(server_side=server_side, flat_kwargs=ssl_kwargs)
|
|
103
159
|
return res, socket_kwargs
|
|
160
|
+
|
|
161
|
+
def do_ssl_handshake(self):
|
|
162
|
+
# Handshake management is performed differently with blocking or non-blocking connection
|
|
163
|
+
# See BlockingSocketClient and NonBlockingSocketClient for implementation
|
|
164
|
+
raise NotImplementedError()
|
|
165
|
+
|
|
166
|
+
def ensure_ssl_handshake_is_done(self):
|
|
167
|
+
if self.is_with_ssl and not self.__is_ssl_handshake_done:
|
|
168
|
+
self.do_ssl_handshake()
|
|
169
|
+
self.__is_ssl_handshake_done = True
|
|
104
170
|
|
|
105
171
|
|
|
106
172
|
class SocketClient(Socket):
|
|
@@ -109,10 +175,81 @@ class SocketClient(Socket):
|
|
|
109
175
|
"""
|
|
110
176
|
__metaclass__ = abc.ABCMeta
|
|
111
177
|
|
|
112
|
-
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
|
|
113
|
-
|
|
178
|
+
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument, do_run_with_recv=True, do_run_with_send=True):
|
|
179
|
+
"""Socket client constructor
|
|
180
|
+
@param name: Socket client name
|
|
181
|
+
@param create_ipv4_socket_kwargs: Arguments to create an IPv4 socket
|
|
182
|
+
@param idle_sleep_delay: delay to sleep when socket is idle (used only in some clients to control CPU resource impact ; default: 0.01 s)
|
|
183
|
+
@param do_run_with_recv: Define if recv is done in run process when client is started
|
|
184
|
+
@param do_run_with_send: Define if send is done in run process when client is started
|
|
185
|
+
"""
|
|
186
|
+
# Data used in start processing
|
|
187
|
+
self.__data_lock = threading.Lock()
|
|
188
|
+
self.__data = types.SimpleNamespace(
|
|
189
|
+
in_bytes=b"",
|
|
190
|
+
out_bytes=b"",
|
|
191
|
+
)
|
|
192
|
+
self.__with_recv = do_run_with_recv
|
|
193
|
+
self.__with_send = do_run_with_send
|
|
194
|
+
|
|
195
|
+
# Note: super().__init__ must be done after self.__data & others initialization since it can execute create_ipv4_socket_kwargs method
|
|
196
|
+
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def is_run_with_recv(self):
|
|
200
|
+
return self.__with_recv
|
|
114
201
|
|
|
115
|
-
|
|
202
|
+
@property
|
|
203
|
+
def is_run_with_send(self):
|
|
204
|
+
return self.__with_send
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def _data_lock(self):
|
|
208
|
+
return self.__data_lock
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def _data(self):
|
|
212
|
+
return self.__data
|
|
213
|
+
|
|
214
|
+
def _delete_object(self):
|
|
215
|
+
self.stop()
|
|
216
|
+
super()._delete_object()
|
|
217
|
+
|
|
218
|
+
def _start_thread(self, thread):
|
|
219
|
+
# Ensure SSL handshake is done before starting to receive and send data
|
|
220
|
+
self.ensure_ssl_handshake_is_done()
|
|
221
|
+
|
|
222
|
+
# Start thread
|
|
223
|
+
super()._start_thread(thread)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def received_data_size(self):
|
|
227
|
+
with self.__data_lock:
|
|
228
|
+
res = len(self.__data.in_bytes)
|
|
229
|
+
return res
|
|
230
|
+
|
|
231
|
+
def read(self, bufsize=1024, **kwargs):
|
|
232
|
+
if self.is_started and self.is_run_with_recv:
|
|
233
|
+
with self.__data_lock:
|
|
234
|
+
if self.__data.in_bytes:
|
|
235
|
+
res = self.__data.in_bytes[:bufsize]
|
|
236
|
+
self.__data.in_bytes = self.__data.in_bytes[bufsize:]
|
|
237
|
+
else:
|
|
238
|
+
res = b''
|
|
239
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
240
|
+
logger.debug(f"[{self.name}] Read data from received data: [{res}] (type: {type(res)} ; remaining data: {len(self.__data.in_bytes)})")
|
|
241
|
+
return res
|
|
242
|
+
else:
|
|
243
|
+
return super().read(bufsize=bufsize, **kwargs)
|
|
244
|
+
|
|
245
|
+
def write(self, data_bytes, loop_until_all_is_sent=True, **kwargs):
|
|
246
|
+
if self.is_started and self.is_run_with_send:
|
|
247
|
+
with self.__data_lock:
|
|
248
|
+
self.__data.out_bytes += data_bytes
|
|
249
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
250
|
+
logger.debug(f"[{self.name}] Added data in data to send (total: {len(self.__data.out_bytes)})")
|
|
251
|
+
else:
|
|
252
|
+
return super().write(data_bytes, loop_until_all_is_sent=loop_until_all_is_sent, **kwargs)
|
|
116
253
|
|
|
117
254
|
|
|
118
255
|
class SocketServer(Socket):
|
|
@@ -121,8 +258,8 @@ class SocketServer(Socket):
|
|
|
121
258
|
"""
|
|
122
259
|
__metaclass__ = abc.ABCMeta
|
|
123
260
|
|
|
124
|
-
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
|
|
125
|
-
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
|
|
261
|
+
def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
|
|
262
|
+
super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
|
|
126
263
|
|
|
127
264
|
def accept(self):
|
|
128
265
|
conn, addr = self.internal_socket.accept()
|
|
@@ -131,17 +268,7 @@ class SocketServer(Socket):
|
|
|
131
268
|
return res, addr
|
|
132
269
|
|
|
133
270
|
@abc.abstractmethod
|
|
134
|
-
def
|
|
135
|
-
"""Start server.
|
|
136
|
-
"""
|
|
137
|
-
raise NotImplementedError()
|
|
138
|
-
|
|
139
|
-
@abc.abstractmethod
|
|
140
|
-
def stop(self):
|
|
141
|
-
raise NotImplementedError()
|
|
142
|
-
|
|
143
|
-
@abc.abstractmethod
|
|
144
|
-
def _process_data(self, data):
|
|
271
|
+
def _process_received_data(self, data):
|
|
145
272
|
raise NotImplementedError()
|
|
146
273
|
|
|
147
274
|
|
|
@@ -21,7 +21,7 @@ from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
|
|
|
21
21
|
from holado_value.common.tables.converters.value_table_converter import ValueTableConverter
|
|
22
22
|
from holado_python.standard_library.socket.blocking_socket import TCPBlockingSocketClient
|
|
23
23
|
from holado_python.standard_library.socket.echo_server import EchoTCPBlockingSocketServer
|
|
24
|
-
from holado_python.standard_library.socket.message_socket import
|
|
24
|
+
from holado_python.standard_library.socket.message_socket import MessageTCPNonBlockingSocketClient, MessageTCPBlockingSocketClient
|
|
25
25
|
from holado_core.common.handlers.wait import WaitEndChange
|
|
26
26
|
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
27
27
|
from holado_python.standard_library.socket.non_blocking_socket import TCPNonBlockingSocketClient
|
|
@@ -74,7 +74,11 @@ def step_impl(context, var_name):
|
|
|
74
74
|
kwargs = ValueTableConverter.convert_name_value_table_2_dict(table)
|
|
75
75
|
|
|
76
76
|
separator = kwargs.pop('separator') if 'separator' in kwargs else '\n'
|
|
77
|
-
|
|
77
|
+
blocking = kwargs.pop('blocking') if 'blocking' in kwargs else True
|
|
78
|
+
if blocking:
|
|
79
|
+
res = MessageTCPBlockingSocketClient(separator, create_ipv4_socket_kwargs=kwargs)
|
|
80
|
+
else:
|
|
81
|
+
res = MessageTCPNonBlockingSocketClient(separator, create_ipv4_socket_kwargs=kwargs)
|
|
78
82
|
|
|
79
83
|
__get_variable_manager().register_variable(var_name, res)
|
|
80
84
|
|
|
@@ -139,6 +143,14 @@ def step_impl(context, server_varname):
|
|
|
139
143
|
################################################################
|
|
140
144
|
|
|
141
145
|
|
|
146
|
+
@Step(r"ensure SSL handshake is done \(socket: (?P<socket_varname>{Variable})\)")
|
|
147
|
+
def step_impl(context, socket_varname):
|
|
148
|
+
"""Ensure SSL handshake is done.
|
|
149
|
+
"""
|
|
150
|
+
sock = StepTools.evaluate_scenario_parameter(socket_varname)
|
|
151
|
+
|
|
152
|
+
sock.ensure_ssl_handshake_is_done()
|
|
153
|
+
|
|
142
154
|
@Step(r"write (?P<data>{Bytes}) \(socket: (?P<socket_varname>{Variable})\)")
|
|
143
155
|
def step_impl(context, data, socket_varname):
|
|
144
156
|
"""Send data
|
|
@@ -216,7 +228,7 @@ def step_impl(context, var_name, socket_varname):
|
|
|
216
228
|
var_name = StepTools.evaluate_variable_name(var_name)
|
|
217
229
|
sock = StepTools.evaluate_scenario_parameter(socket_varname)
|
|
218
230
|
|
|
219
|
-
res =
|
|
231
|
+
res = sock.nb_messages
|
|
220
232
|
|
|
221
233
|
__get_variable_manager().register_variable(var_name, res)
|
|
222
234
|
|
|
@@ -21,7 +21,7 @@ from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
|
|
|
21
21
|
from holado_value.common.tables.converters.value_table_converter import ValueTableConverter
|
|
22
22
|
from holado_python.standard_library.socket.blocking_socket import TCPBlockingSocketClient
|
|
23
23
|
from holado_python.standard_library.socket.echo_server import EchoTCPBlockingSocketServer
|
|
24
|
-
from holado_python.standard_library.socket.message_socket import
|
|
24
|
+
from holado_python.standard_library.socket.message_socket import MessageTCPNonBlockingSocketClient
|
|
25
25
|
from holado_core.common.handlers.wait import WaitFuncResultVerifying, WaitEndChange
|
|
26
26
|
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
27
27
|
from holado_python.standard_library.ssl.ssl import SslManager
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
#################################################
|
|
3
|
+
# HolAdo (Holistic Automation do)
|
|
4
|
+
#
|
|
5
|
+
# (C) Copyright 2021-2025 by Eric Klumpp
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
#
|
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
# The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
12
|
+
#################################################
|
|
13
|
+
|
|
14
|
+
from holado_scripting.text.base.base_function import BaseFunction
|
|
15
|
+
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
16
|
+
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
17
|
+
import logging
|
|
18
|
+
from holado_core.common.tools.tools import Tools
|
|
19
|
+
from holado_python.standard_library.typing import Typing
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FunctionApplyFunction(BaseFunction):
|
|
25
|
+
|
|
26
|
+
def __init__(self, func, text_interpreter, var_manager):
|
|
27
|
+
self.__func = func
|
|
28
|
+
self.__text_interpreter = text_interpreter
|
|
29
|
+
self.__variable_manager = var_manager
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def apply(self, args):
|
|
33
|
+
# Verify arguments
|
|
34
|
+
if not isinstance(args, list):
|
|
35
|
+
raise TechnicalException("Arguments must be a list")
|
|
36
|
+
if len(args) != 1:
|
|
37
|
+
raise FunctionalException(f"Function applying a function requires one argument.")
|
|
38
|
+
|
|
39
|
+
src = args[0]
|
|
40
|
+
res = src
|
|
41
|
+
|
|
42
|
+
if isinstance(res, str):
|
|
43
|
+
# Interpret source
|
|
44
|
+
res = self.__text_interpreter.interpret(res)
|
|
45
|
+
|
|
46
|
+
# Manage if source is a variable expression
|
|
47
|
+
if self.__variable_manager.exists_variable(expression=res):
|
|
48
|
+
res = self.__variable_manager.get_value(res)
|
|
49
|
+
|
|
50
|
+
# Remove possible quotes
|
|
51
|
+
if isinstance(res, str):
|
|
52
|
+
if res.startswith("'") and res.endswith("'") or res.startswith('"') and res.endswith('"'):
|
|
53
|
+
res = res.strip("'\"")
|
|
54
|
+
|
|
55
|
+
res = self.__func(res)
|
|
56
|
+
|
|
57
|
+
if Tools.do_log(logger, logging.DEBUG):
|
|
58
|
+
logger.debug(f"[FunctionApplyFunction({self.__func})] [{src}] (type: {Typing.get_object_class_fullname(src)}) -> [{res}] (type: {Typing.get_object_class_fullname(res)})")
|
|
59
|
+
return res
|
|
60
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
#################################################
|
|
3
|
+
# HolAdo (Holistic Automation do)
|
|
4
|
+
#
|
|
5
|
+
# (C) Copyright 2021-2025 by Eric Klumpp
|
|
6
|
+
#
|
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
8
|
+
#
|
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
# The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
|
|
12
|
+
#################################################
|
|
13
|
+
|
|
14
|
+
from holado_scripting.text.base.base_function import BaseFunction
|
|
15
|
+
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
16
|
+
from holado_core.common.exceptions.functional_exception import FunctionalException
|
|
17
|
+
from holado_core.common.tools.string_tools import StrTools
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FunctionToString(BaseFunction):
|
|
21
|
+
|
|
22
|
+
def __init__(self, text_interpreter, var_manager):
|
|
23
|
+
self.__text_interpreter = text_interpreter
|
|
24
|
+
self.__variable_manager = var_manager
|
|
25
|
+
|
|
26
|
+
def apply(self, args):
|
|
27
|
+
from holado_test.scenario.step_tools import StepTools
|
|
28
|
+
|
|
29
|
+
# Verify arguments
|
|
30
|
+
if not isinstance(args, list):
|
|
31
|
+
raise TechnicalException("Arguments must be a list")
|
|
32
|
+
if len(args) != 1:
|
|
33
|
+
raise FunctionalException("Function 'ToBytes' requires one argument.")
|
|
34
|
+
|
|
35
|
+
src = args[0]
|
|
36
|
+
|
|
37
|
+
# Interpret source
|
|
38
|
+
if isinstance(src, str):
|
|
39
|
+
src = StepTools.extract_string_value(src)
|
|
40
|
+
src = self.__text_interpreter.interpret(src)
|
|
41
|
+
|
|
42
|
+
# Manage if source is a variable expression
|
|
43
|
+
if isinstance(src, str) and self.__variable_manager.exists_variable(expression=src):
|
|
44
|
+
src = self.__variable_manager.get_value(src)
|
|
45
|
+
|
|
46
|
+
# Convert source to bytes
|
|
47
|
+
res = StrTools.to_string(src)
|
|
48
|
+
|
|
49
|
+
return res
|
|
50
|
+
|
|
@@ -30,6 +30,8 @@ from holado_scripting.text.interpreter.functions.function_exists_variable import
|
|
|
30
30
|
from holado_scripting.text.interpreter.functions.function_convert import FunctionConvert
|
|
31
31
|
from holado_python.common.tools.datetime import DateTime
|
|
32
32
|
from holado_python.standard_library.typing import Typing
|
|
33
|
+
from holado_scripting.text.interpreter.functions.function_to_string import FunctionToString
|
|
34
|
+
from holado_scripting.text.interpreter.functions.function_apply_function import FunctionApplyFunction
|
|
33
35
|
|
|
34
36
|
logger = logging.getLogger(__name__)
|
|
35
37
|
|
|
@@ -193,6 +195,7 @@ class TextInterpreter(TextInspecter):
|
|
|
193
195
|
return res
|
|
194
196
|
|
|
195
197
|
def __register_default_functions(self, dynamic_text_manager):
|
|
198
|
+
self.register_function("len", FunctionApplyFunction(len, self, self._variable_manager))
|
|
196
199
|
self.register_function("DynamicValue", FunctionDynamicValue(dynamic_text_manager))
|
|
197
200
|
self.register_function("ExistsVariable", FunctionExistsVariable(self._variable_manager))
|
|
198
201
|
|
|
@@ -206,6 +209,7 @@ class TextInterpreter(TextInspecter):
|
|
|
206
209
|
self.register_function("ToBase64", FunctionToBase64(self, self._variable_manager))
|
|
207
210
|
self.register_function("ToBytes", FunctionToBytes(self, self._variable_manager))
|
|
208
211
|
self.register_function("ToHex", FunctionToHex(self, self._variable_manager))
|
|
212
|
+
self.register_function("ToString", FunctionToString(self, self._variable_manager))
|
|
209
213
|
self.register_function("DatetimeUTCToTAI", FunctionConvert(lambda dt: DateTime.datetime_utc_to_tai(dt), self, self._variable_manager))
|
|
210
214
|
self.register_function("DatetimeTAIToUTC", FunctionConvert(lambda dt: DateTime.datetime_tai_to_utc(dt), self, self._variable_manager))
|
|
211
215
|
|
|
@@ -34,6 +34,7 @@ from holado_scripting.common.tools.evaluate_parameters import EvaluateParameters
|
|
|
34
34
|
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
35
35
|
from holado.holado_config import Config
|
|
36
36
|
from holado_python.standard_library.typing import Typing
|
|
37
|
+
from holado_value.common.tables.value_table_manager import ValueTableManager
|
|
37
38
|
|
|
38
39
|
logger = logging.getLogger(__name__)
|
|
39
40
|
|
|
@@ -237,7 +238,7 @@ class StepTools(object):
|
|
|
237
238
|
cell_content = res.header.get_cell(0).content
|
|
238
239
|
if cls._get_variable_manager().exists_variable(variable_name=cell_content):
|
|
239
240
|
value = cls._get_variable_manager().get_variable_value(cell_content)
|
|
240
|
-
if
|
|
241
|
+
if ValueTableManager.is_value_table(value):
|
|
241
242
|
res = value
|
|
242
243
|
elif isinstance(value, TableWithHeader):
|
|
243
244
|
res = ValueTableConverter.convert_table_with_header_2_value_table_with_header(value, do_eval_once=do_eval_once)
|
|
@@ -246,7 +247,7 @@ class StepTools(object):
|
|
|
246
247
|
elif isinstance(table, Table):
|
|
247
248
|
if res.nb_columns == 1 and res.nb_rows == 1:
|
|
248
249
|
value = res.get_row(0).get_cell(0).value
|
|
249
|
-
if
|
|
250
|
+
if ValueTableManager.is_value_table(value):
|
|
250
251
|
res = value
|
|
251
252
|
elif isinstance(value, Table):
|
|
252
253
|
res = ValueTableConverter.convert_table_2_value_table(value, do_eval_once=do_eval_once)
|
|
@@ -498,7 +499,7 @@ class StepTools(object):
|
|
|
498
499
|
r"{Variable}|b'[^']*'".format(Variable=cls.get_registered_type_pattern('Variable')),
|
|
499
500
|
StepTools.evaluate_scenario_parameter)
|
|
500
501
|
cls.register_type('Str',
|
|
501
|
-
r"{Variable}|'[^']*'{suffix}".format(Variable=cls.get_registered_type_pattern('Variable'), suffix=regex_suffix),
|
|
502
|
+
r"{Variable}|r?'[^']*'{suffix}".format(Variable=cls.get_registered_type_pattern('Variable'), suffix=regex_suffix),
|
|
502
503
|
StepTools.evaluate_scenario_parameter)
|
|
503
504
|
cls.register_type('RawStr',
|
|
504
505
|
r"{Variable}|'.*'{suffix}".format(Variable=cls.get_registered_type_pattern('Variable'), suffix=regex_suffix),
|
|
@@ -107,7 +107,8 @@ class Table2ValueTable_CellComparator(TableCellComparator):
|
|
|
107
107
|
|
|
108
108
|
cause = None
|
|
109
109
|
try:
|
|
110
|
-
value_type = cell_2.value_type
|
|
110
|
+
# value_type = cell_2.value_type
|
|
111
|
+
value_type = cell_2.content_type
|
|
111
112
|
if value_type == ValueTypes.NotApplicable:
|
|
112
113
|
res = True
|
|
113
114
|
elif value_type == ValueTypes.Null:
|
|
@@ -63,6 +63,11 @@ class ValueTableCell(TableCell):
|
|
|
63
63
|
def value(self):
|
|
64
64
|
return self.__value.value
|
|
65
65
|
|
|
66
|
+
@value.setter
|
|
67
|
+
def value(self, value):
|
|
68
|
+
self.__value = Value(original_value=None, value=value, do_eval_once=self.__value.do_eval_once)
|
|
69
|
+
self.content = self.__value.original_value
|
|
70
|
+
|
|
66
71
|
def get_value(self, raise_if_undefined=False):
|
|
67
72
|
return self.__value.get_value(raise_if_undefined=raise_if_undefined)
|
|
68
73
|
|