holado 0.2.7__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.

Files changed (24) hide show
  1. {holado-0.2.7.dist-info → holado-0.2.8.dist-info}/METADATA +4 -1
  2. {holado-0.2.7.dist-info → holado-0.2.8.dist-info}/RECORD +24 -22
  3. holado_core/common/tables/table.py +2 -2
  4. holado_core/common/tools/string_tools.py +9 -0
  5. holado_core/tests/behave/steps/common/tables_steps.py +9 -2
  6. holado_helper/script/action.py +16 -7
  7. holado_python/standard_library/socket/blocking_socket.py +86 -29
  8. holado_python/standard_library/socket/echo_server.py +4 -3
  9. holado_python/standard_library/socket/message_socket.py +59 -20
  10. holado_python/standard_library/socket/non_blocking_socket.py +58 -60
  11. holado_python/standard_library/socket/socket.py +149 -22
  12. holado_python/tests/behave/steps/standard_library/socket_steps.py +15 -3
  13. holado_python/tests/behave/steps/standard_library/ssl_steps.py +1 -1
  14. holado_scripting/text/interpreter/functions/function_apply_function.py +60 -0
  15. holado_scripting/text/interpreter/functions/function_to_string.py +50 -0
  16. holado_scripting/text/interpreter/text_interpreter.py +4 -0
  17. holado_test/scenario/step_tools.py +4 -3
  18. holado_value/common/tables/value_table_cell.py +5 -0
  19. holado_value/common/tables/value_table_row.py +0 -1
  20. holado_value/common/tools/value.py +5 -1
  21. test_holado/features/NonReg/holado_python/standard_library/socket.feature +62 -0
  22. test_holado/features/NonReg/holado_python/standard_library/socket_with_ssl.feature +77 -1
  23. {holado-0.2.7.dist-info → holado-0.2.8.dist-info}/WHEEL +0 -0
  24. {holado-0.2.7.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 types
21
- import threading
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
- self.__data_lock = threading.Lock()
41
- self.__data = types.SimpleNamespace(
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.__data)
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
- self.__start_thread = LoopFunctionThreaded(self._wait_and_process_events, kwargs=kwargs, register_thread=True, delay_before_run_sec=None)
75
- self.__start_thread.start()
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
- recv_data = sock.recv(read_bufsize, **read_kwargs)
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
- with self.__data_lock: # data is self.__data
90
+ has_activity = True
91
+ with self._data_lock: # data is self._data
98
92
  data.in_bytes += recv_data
99
- if mask & selectors.EVENT_WRITE:
100
- with self.__data_lock: # data is self.__data
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
- data.out_bytes = data.out_bytes[sent:]
104
-
105
- @property
106
- def received_data_size(self):
107
- with self.__data_lock:
108
- res = len(self.__data.in_bytes)
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 write(self, data_bytes):
121
- with self.__data_lock:
122
- self.__data.out_bytes += data_bytes
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
- sock = ssl_context.wrap_socket(sock, server_hostname=host, do_handshake_on_connect=False)
141
- self._set_internal_socket(sock)
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
- def _set_internal_socket(self, socket):
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 at shutdown): {exc}")
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
- self.__socket.close()
76
- self.__socket = None
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
- res = self.internal_socket.recv(bufsize, **kwargs)
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
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
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 start(self, read_bufsize=1024, *, read_kwargs=None, write_kwargs=None):
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 MessageTCPSocketClient
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
- res = MessageTCPSocketClient(separator, create_ipv4_socket_kwargs=kwargs)
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 = len(sock.messages)
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 MessageTCPSocketClient
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 isinstance(value, ValueTableWithHeader):
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 isinstance(value, ValueTable):
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),
@@ -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
 
@@ -16,7 +16,6 @@ from holado_value.common.tables.value_table_cell import ValueTableCell
16
16
  from holado_core.common.tables.table_row import TableRow
17
17
  import logging
18
18
  from holado_core.common.tools.tools import Tools
19
- from holado_python.standard_library.typing import Typing
20
19
  from holado.common.handlers.undefined import undefined_value
21
20
 
22
21
  logger = logging.getLogger(__name__)