holado 0.2.7__py3-none-any.whl → 0.3.0__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 (118) hide show
  1. holado/common/handlers/undefined.py +7 -1
  2. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/METADATA +7 -1
  3. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/RECORD +116 -107
  4. holado_ais/ais/ais_messages.py +97 -6
  5. holado_ais/tests/behave/steps/ais/ais_manager_steps.py +1 -1
  6. holado_ais/tests/behave/steps/ais/ais_messages_steps.py +45 -6
  7. holado_binary/ipc/bit_series.py +3 -3
  8. holado_binary/tests/behave/steps/ipc/binary_steps.py +1 -1
  9. holado_binary/tests/behave/steps/ipc/bit_series_steps.py +4 -3
  10. holado_context/tests/behave/steps/private/common/context_steps.py +1 -1
  11. holado_core/common/resource/persisted_data_manager.py +13 -16
  12. holado_core/common/resource/resource_manager.py +10 -10
  13. holado_core/common/tables/converters/table_converter.py +47 -9
  14. holado_core/common/tables/table.py +2 -2
  15. holado_core/common/tables/table_manager.py +6 -7
  16. holado_core/common/tables/table_with_header.py +6 -0
  17. holado_core/common/tools/string_tools.py +9 -0
  18. holado_core/tests/behave/steps/common/common_steps.py +2 -1
  19. holado_core/tests/behave/steps/common/config_steps.py +1 -1
  20. holado_core/tests/behave/steps/common/resource_steps.py +1 -1
  21. holado_core/tests/behave/steps/common/tables_steps.py +27 -4
  22. holado_data/data/generator/generator_manager.py +39 -0
  23. holado_data/tests/behave/steps/data/generator_steps.py +1 -1
  24. holado_data/tests/behave/steps/tools/utils_steps.py +1 -2
  25. holado_db/tests/behave/steps/tools/db/db_client_steps.py +1 -1
  26. holado_db/tests/behave/steps/tools/db/postgresql_client_steps.py +1 -1
  27. holado_db/tests/behave/steps/tools/db/sqlite_client_steps.py +1 -1
  28. holado_db/tools/db/clients/base/db_client.py +81 -28
  29. holado_db/tools/db/clients/postgresql/postgresql_client.py +17 -7
  30. holado_db/tools/db/query/base/query_builder.py +58 -7
  31. holado_db/tools/db/query/pypika/pypika_query_builder.py +73 -21
  32. holado_docker/tests/behave/steps/tools/docker_steps.py +1 -1
  33. holado_grpc/tests/behave/steps/api/grpc_client_steps.py +1 -1
  34. holado_grpc/tests/behave/steps/private/api/grpc_steps.py +1 -1
  35. holado_helper/script/action.py +16 -7
  36. holado_json/tests/behave/steps/ipc/json_steps.py +1 -1
  37. holado_keycloak/tests/behave/steps/tools/keycloak_client_steps.py +1 -1
  38. holado_multitask/tests/behave/steps/multiprocessing_steps.py +1 -1
  39. holado_multitask/tests/behave/steps/multithreading_steps.py +1 -1
  40. holado_protobuf/ipc/protobuf/types/google/protobuf.py +1 -1
  41. holado_protobuf/tests/behave/steps/ipc/protobuf_steps.py +1 -1
  42. holado_python/common/tools/datetime.py +31 -12
  43. holado_python/standard_library/socket/blocking_socket.py +112 -42
  44. holado_python/standard_library/socket/echo_server.py +4 -3
  45. holado_python/standard_library/socket/message_socket.py +69 -22
  46. holado_python/standard_library/socket/non_blocking_socket.py +65 -67
  47. holado_python/standard_library/socket/socket.py +272 -32
  48. holado_python/standard_library/ssl/resources/certificates/NOTES.txt +1 -1
  49. holado_python/standard_library/ssl/resources/certificates/rootCACert.pem +24 -0
  50. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +21 -0
  51. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +28 -0
  52. holado_python/standard_library/ssl/ssl.py +138 -21
  53. holado_python/tests/behave/steps/convert_steps.py +1 -1
  54. holado_python/tests/behave/steps/iterable_steps.py +1 -1
  55. holado_python/tests/behave/steps/standard_library/csv_steps.py +1 -1
  56. holado_python/tests/behave/steps/standard_library/datetime_steps.py +1 -1
  57. holado_python/tests/behave/steps/standard_library/hashlib_steps.py +1 -1
  58. holado_python/tests/behave/steps/standard_library/multiprocessing_steps.py +1 -1
  59. holado_python/tests/behave/steps/standard_library/queue_steps.py +1 -1
  60. holado_python/tests/behave/steps/standard_library/socket_steps.py +147 -21
  61. holado_python/tests/behave/steps/standard_library/ssl_steps.py +87 -16
  62. holado_rabbitmq/tests/behave/steps/tools/rabbitmq_client_steps.py +48 -20
  63. holado_rabbitmq/tests/behave/steps/tools/rabbitmq_server_steps.py +1 -1
  64. holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +19 -13
  65. holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +2 -29
  66. holado_redis/tests/behave/steps/tools/redis_client_steps.py +1 -1
  67. holado_rest/tests/behave/steps/api/rest_client_steps.py +1 -1
  68. holado_rest/tests/behave/steps/private/api/rest_steps.py +1 -1
  69. holado_s3/tests/behave/steps/private/tools/s3_steps.py +1 -1
  70. holado_s3/tests/behave/steps/tools/s3_client_steps.py +1 -1
  71. holado_s3/tests/behave/steps/tools/s3_server_steps.py +1 -1
  72. holado_scripting/tests/behave/steps/common/tools/variable_convert_steps.py +3 -2
  73. holado_scripting/tests/behave/steps/common/tools/variable_new_steps.py +1 -1
  74. holado_scripting/tests/behave/steps/common/tools/variable_steps.py +1 -1
  75. holado_scripting/tests/behave/steps/common/tools/variable_verify_steps.py +1 -1
  76. holado_scripting/tests/behave/steps/scenario/function_steps.py +1 -1
  77. holado_scripting/tests/behave/steps/scenario/if_steps.py +1 -1
  78. holado_scripting/tests/behave/steps/scenario/loop_steps.py +1 -1
  79. holado_scripting/text/interpreter/functions/function_apply_function.py +60 -0
  80. holado_scripting/text/interpreter/functions/function_to_string.py +50 -0
  81. holado_scripting/text/interpreter/text_interpreter.py +4 -0
  82. holado_sftp/tests/behave/steps/private/tools/sftp_steps.py +1 -1
  83. holado_sftp/tests/behave/steps/tools/sftp_client_steps.py +1 -1
  84. holado_sftp/tests/behave/steps/tools/sftp_server_steps.py +1 -1
  85. holado_swagger/tests/behave/steps/swagger_hub/mockserver_steps.py +1 -1
  86. holado_system/system/command/command.py +14 -9
  87. holado_system/tests/behave/steps/system/commands_steps.py +1 -1
  88. holado_system/tests/behave/steps/system/file_steps.py +1 -1
  89. holado_system/tests/behave/steps/system/system_steps.py +1 -1
  90. holado_test/scenario/step_tools.py +5 -4
  91. holado_test/scenario/tester_tools.py +6 -3
  92. holado_test/tests/behave/steps/scenario/exception_steps.py +1 -1
  93. holado_test/tests/behave/steps/scenario/scenario_steps.py +1 -1
  94. holado_test/tests/behave/steps/scenario/tester_steps.py +4 -4
  95. holado_value/common/tables/converters/value_table_converter.py +52 -8
  96. holado_value/common/tables/value_table_cell.py +5 -0
  97. holado_value/common/tables/value_table_manager.py +0 -10
  98. holado_value/common/tables/value_table_row.py +0 -1
  99. holado_value/common/tools/value.py +5 -1
  100. holado_ws/tests/behave/steps/api/web_service_steps.py +1 -1
  101. holado_yaml/tests/behave/steps/yaml_steps.py +1 -1
  102. holado_yaml/yaml/yaml_manager.py +2 -2
  103. test_holado/features/NonReg/common/tables/table.feature +30 -24
  104. test_holado/features/NonReg/holado_ais/ais_message-bitarray_to_nmea.feature +1 -1
  105. test_holado/features/NonReg/holado_python/standard_library/socket/local_echo_server/socket_reset.feature +191 -0
  106. test_holado/features/NonReg/holado_python/standard_library/{socket_with_ssl.feature → socket/local_echo_server/socket_with_tls_and_verify.feature} +126 -27
  107. test_holado/features/NonReg/holado_python/standard_library/socket/local_echo_server/socket_with_tls_without_verify.feature +299 -0
  108. test_holado/features/NonReg/holado_python/standard_library/{socket.feature → socket/local_echo_server/socket_without_tls.feature} +63 -1
  109. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_with_mtls.feature +214 -0
  110. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_with_tls.feature +184 -0
  111. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_without_tls.feature +169 -0
  112. test_holado/features/NonReg/tools/RabbitMQ.feature +9 -9
  113. test_holado/features/NonReg/tools/RabbitMQ_steps.feature +8 -8
  114. test_holado/logging.conf +5 -3
  115. holado_core/common/transport/__init__.py +0 -0
  116. holado_core/common/transport/crc.py +0 -40
  117. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/WHEEL +0 -0
  118. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,10 +13,10 @@
13
13
 
14
14
  import logging
15
15
  from holado_core.common.exceptions.technical_exception import TechnicalException
16
- from holado_db.tools.db.query.base.query_builder import QueryBuilder
16
+ from holado_db.tools.db.query.base.query_builder import QueryBuilder, DBCompareOperator
17
17
  from holado_core.common.tools.tools import Tools
18
- from holado_core.common.tools.comparators.comparator import CompareOperator
19
18
  from holado_python.standard_library.typing import Typing
19
+ from pypika.terms import Function
20
20
 
21
21
 
22
22
  logger = logging.getLogger(__name__)
@@ -44,10 +44,11 @@ class PypikaQueryBuilder(QueryBuilder):
44
44
  def __init__(self, name, db_client=None):
45
45
  super().__init__(name, db_client)
46
46
 
47
- def select(self, table_name, where_data: dict=None, sql_return="*"):
47
+ def select(self, table_name, where_data: dict=None, where_compare_data: list=None, sql_return="*"):
48
48
  """
49
49
  Simple query & values builder of a select by filtering on given where data.
50
- Parameter 'where_data' has to be a dictionary with keys equal to table column names.
50
+ @param where_data: dictionary of (field_name, value) for simple where clauses.
51
+ @param where_compare_data: list of tuples (field_name, operator, value) for where clauses comparing fields with values.
51
52
  """
52
53
  table = Table(table_name)
53
54
  res = Query.from_(table)
@@ -62,13 +63,15 @@ class PypikaQueryBuilder(QueryBuilder):
62
63
 
63
64
  if where_data:
64
65
  res, values = self.where(res, values, where_data)
65
-
66
+ if where_compare_data:
67
+ res, values = self.where_compare(res, values, list_of_field_operator_value=where_compare_data)
68
+
66
69
  return res, values
67
70
 
68
71
  def insert(self, table_name, data: dict):
69
72
  """
70
73
  Simple query & values builder of an insert of given data.
71
- Parameter 'data' has to be a dictionary with keys equal to table column names.
74
+ @param data: data to insert as dictionary of (field_name, value).
72
75
  """
73
76
  col_names = tuple(sorted(data.keys()))
74
77
  values = tuple((data[c] for c in col_names))
@@ -79,29 +82,39 @@ class PypikaQueryBuilder(QueryBuilder):
79
82
 
80
83
  return res, values
81
84
 
82
- def update(self, table_name, data: dict, where_data: dict):
85
+ def update(self, table_name, data: dict, where_data: dict=None, where_compare_data: list=None):
83
86
  """
84
87
  Simple query & values builder of an update of given data.
85
- Parameters 'data' and 'where_data' have to be dictionaries with keys equal to table column names.
88
+ @param data: data to update as dictionary of (field_name, value).
89
+ @param where_data: dictionary of (field_name, value) for simple where clauses.
90
+ @param where_compare_data: list of tuples (field_name, operator, value) for where clauses comparing fields with values.
86
91
  """
87
92
  col_names = tuple(sorted(data.keys()))
88
- values = tuple((data[c] for c in col_names))
89
93
  sql_placeholder = self.db_client._get_sql_placeholder()
90
94
 
91
95
  table = Table(table_name)
92
96
  res = Query.update(table)
97
+ values = []
93
98
  for c in col_names:
94
- res = res.set(c, Parameter(sql_placeholder))
99
+ val = data[c]
100
+ if isinstance(val, Function):
101
+ res = res.set(c, val)
102
+ else:
103
+ res = res.set(c, Parameter(sql_placeholder))
104
+ values.append(val)
95
105
 
96
106
  if where_data:
97
107
  res, values = self.where(res, values, where_data)
108
+ if where_compare_data:
109
+ res, values = self.where_compare(res, values, list_of_field_operator_value=where_compare_data)
98
110
 
99
111
  return res, values
100
112
 
101
- def delete(self, table_name, where_data: dict=None):
113
+ def delete(self, table_name, where_data: dict=None, where_compare_data: list=None):
102
114
  """
103
115
  Simple query & values builder of a delete by filtering on given where data.
104
- Parameter 'where_data' has to be a dictionary with keys equal to table column names.
116
+ @param where_data: dictionary of (field_name, value) for simple where clauses.
117
+ @param where_compare_data: list of tuples (field_name, operator, value) for where clauses comparing fields with values.
105
118
  """
106
119
  table = Table(table_name)
107
120
  res = Query.from_(table).delete()
@@ -109,18 +122,25 @@ class PypikaQueryBuilder(QueryBuilder):
109
122
 
110
123
  if where_data:
111
124
  res, values = self.where(res, values, where_data)
125
+ if where_compare_data:
126
+ res, values = self.where_compare(res, values, list_of_field_operator_value=where_compare_data)
112
127
 
113
128
  return res, values
114
129
 
115
130
  def where(self, query, values, where_data: dict):
116
131
  col_names = tuple(sorted(where_data.keys()))
117
- where_values = tuple((where_data[c] for c in col_names))
118
132
  sql_placeholder = self.db_client._get_sql_placeholder()
119
133
 
120
134
  res = query
121
135
  table = self.__get_table(query)
136
+ where_values = []
122
137
  for c in col_names:
123
- res = res.where(getattr(table, c) == Parameter(sql_placeholder))
138
+ val = where_data[c]
139
+ if val is None:
140
+ res, where_values = self.where_is_null(res, where_values, c)
141
+ else:
142
+ res = res.where(getattr(table, c) == Parameter(sql_placeholder))
143
+ where_values.append(where_data[c])
124
144
 
125
145
  if values is not None:
126
146
  values = (*values, *where_values)
@@ -128,21 +148,31 @@ class PypikaQueryBuilder(QueryBuilder):
128
148
  values = where_values
129
149
  return res, values
130
150
 
131
- def where_compare(self, query, values, field_name, operator:CompareOperator, value):
151
+ def where_compare(self, query, values, *, field_name=None, operator:DBCompareOperator=None, value=None, list_of_field_operator_value=None):
152
+ if list_of_field_operator_value is not None:
153
+ return super().where_compare(query, values, list_of_field_operator_value=list_of_field_operator_value)
154
+
155
+ # Manage operators calling other methods
156
+ if operator == DBCompareOperator.In:
157
+ return self.where_in(query, values, field_name, value, not_in=False)
158
+ elif operator == DBCompareOperator.NotIn:
159
+ return self.where_in(query, values, field_name, value, not_in=True)
160
+
161
+ # Manage other operators
132
162
  table = self.__get_table(query)
133
163
  sql_placeholder = self.db_client._get_sql_placeholder()
134
164
 
135
- if operator == CompareOperator.Different:
165
+ if operator == DBCompareOperator.Different:
136
166
  res = query.where(getattr(table, field_name) != Parameter(sql_placeholder))
137
- elif operator == CompareOperator.Equal:
167
+ elif operator == DBCompareOperator.Equal:
138
168
  res = query.where(getattr(table, field_name) == Parameter(sql_placeholder))
139
- elif operator == CompareOperator.Inferior:
169
+ elif operator == DBCompareOperator.Inferior:
140
170
  res = query.where(getattr(table, field_name) < Parameter(sql_placeholder))
141
- elif operator == CompareOperator.InferiorOrEqual:
171
+ elif operator == DBCompareOperator.InferiorOrEqual:
142
172
  res = query.where(getattr(table, field_name) <= Parameter(sql_placeholder))
143
- elif operator == CompareOperator.Superior:
173
+ elif operator == DBCompareOperator.Superior:
144
174
  res = query.where(getattr(table, field_name) > Parameter(sql_placeholder))
145
- elif operator == CompareOperator.SuperiorOrEqual:
175
+ elif operator == DBCompareOperator.SuperiorOrEqual:
146
176
  res = query.where(getattr(table, field_name) >= Parameter(sql_placeholder))
147
177
  else:
148
178
  raise TechnicalException(f"Unmanaged compare operator {operator}")
@@ -161,6 +191,14 @@ class PypikaQueryBuilder(QueryBuilder):
161
191
  res = query.where(getattr(table, field_name).isin(field_values))
162
192
  return res, values
163
193
 
194
+ def where_is_null(self, query, values, field_name, is_not_null=False):
195
+ table = self.__get_table(query)
196
+ if is_not_null:
197
+ res = query.where(getattr(table, field_name).notnull())
198
+ else:
199
+ res = query.where(getattr(table, field_name).isnull())
200
+ return res, values
201
+
164
202
  def where_json_value(self, query, values, field_name, key, value, as_text_value=False):
165
203
  table = self.__get_table(query)
166
204
  sql_placeholder = self.db_client._get_sql_placeholder()
@@ -181,6 +219,20 @@ class PypikaQueryBuilder(QueryBuilder):
181
219
  return query.get_sql()
182
220
  else:
183
221
  raise TechnicalException(f"Unmanaged query of type {Typing.get_object_class_fullname(query)}")
222
+
223
+ # def is_query_type(self, query, query_type):
224
+ # query_type = query_type.lower()
225
+ #
226
+ # if query_type == 'insert':
227
+ # return query._insert_table is not None
228
+ # elif query_type == 'select':
229
+ # return len(query._selects) > 0
230
+ # elif query_type == 'update':
231
+ # return query._update_table is not None
232
+ # elif query_type == 'delete':
233
+ # return query._delete_from
234
+ # else:
235
+ # raise TechnicalException(f"Unmanaged query type '{query_type}' (managed query types: 'insert', 'select', 'update', 'delete')")
184
236
 
185
237
  def __get_table(self, query):
186
238
  if query._from is not None and len(query._from) > 0:
@@ -14,7 +14,7 @@
14
14
 
15
15
 
16
16
  from holado.common.context.session_context import SessionContext
17
- from holado_test.behave.behave import *
17
+ from holado_test.behave.behave import * # @UnusedWildImport
18
18
  import logging
19
19
 
20
20
  logger = logging.getLogger(__name__)
@@ -15,7 +15,7 @@
15
15
 
16
16
  from holado_test.scenario.step_tools import StepTools
17
17
  from holado.common.context.session_context import SessionContext
18
- from holado_test.behave.behave import *
18
+ from holado_test.behave.behave import * # @UnusedWildImport
19
19
  from holado_grpc.api.rpc.grpc_client import GRpcClient
20
20
  import logging
21
21
  from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
@@ -14,7 +14,7 @@
14
14
 
15
15
 
16
16
  from holado.common.context.session_context import SessionContext
17
- from holado_test.behave.behave import *
17
+ from holado_test.behave.behave import * # @UnusedWildImport
18
18
  import os.path
19
19
  from holado_core.tools.abstracts.blocking_command_service import BlockingCommandService
20
20
  import logging
@@ -89,13 +89,22 @@ class BehaveActionRunner():
89
89
 
90
90
  def __add_variable_values(self, res, params):
91
91
  if params:
92
- for key, value in params.items():
93
- if key == 'STATUS':
94
- continue
95
- if value is not_applicable:
96
- res[key] = 'N/A'
97
- else:
98
- res[key] = value
92
+ if isinstance(params, dict):
93
+ for key, value in params.items():
94
+ self.__add_variable_value(res, key, value)
95
+ elif isinstance(params, list):
96
+ for key, value in params:
97
+ self.__add_variable_value(res, key, value)
98
+ else:
99
+ raise TechnicalException(f"Unexpected params type '{type(params)}'")
100
+
101
+ def __add_variable_value(self, res, var_name, value):
102
+ if var_name == 'STATUS':
103
+ return
104
+ if value is not_applicable:
105
+ res[var_name] = 'N/A'
106
+ else:
107
+ res[var_name] = value
99
108
 
100
109
  def run(self):
101
110
  logger.info(f"Processing action '{self.__action_info.action}' of index {self.__action_info.index}: {self.__action_info.params}")
@@ -15,7 +15,7 @@
15
15
 
16
16
  from holado_test.scenario.step_tools import StepTools
17
17
  from holado.common.context.session_context import SessionContext
18
- from holado_test.behave.behave import *
18
+ from holado_test.behave.behave import * # @UnusedWildImport
19
19
  from holado_json.ipc.json import create_name_value_table_from_json,\
20
20
  create_table_with_header_from_json
21
21
  import logging
@@ -14,7 +14,7 @@
14
14
 
15
15
 
16
16
  from holado.common.context.session_context import SessionContext
17
- from holado_test.behave.behave import *
17
+ from holado_test.behave.behave import * # @UnusedWildImport
18
18
  from holado_keycloak.tools.keycloak.keycloak_client import KeycloakClient
19
19
  import logging
20
20
  from holado_core.common.exceptions.functional_exception import FunctionalException
@@ -15,7 +15,7 @@
15
15
 
16
16
  from holado.common.context.session_context import SessionContext
17
17
  from holado_core.common.exceptions.functional_exception import FunctionalException
18
- from holado_test.behave.behave import *
18
+ from holado_test.behave.behave import * # @UnusedWildImport
19
19
  from holado_core.common.block.function import Function
20
20
  from holado_test.scenario.step_tools import StepTools
21
21
  import logging
@@ -21,7 +21,7 @@ from holado_test.scenario.step_tools import StepTools
21
21
  import logging
22
22
  from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
23
23
  from holado_scripting.common.tools.evaluate_parameters import EvaluateParameters
24
- from holado_test.behave.behave import *
24
+ from holado_test.behave.behave import * # @UnusedWildImport
25
25
  from holado_multitask.multithreading.periodicfunctionthreaded import PeriodicFunctionThreaded
26
26
  from holado.common.handlers.undefined import undefined_argument
27
27
 
@@ -41,7 +41,7 @@ class Duration(Type):
41
41
  elif isinstance(value, timedelta):
42
42
  obj.FromTimedelta(value)
43
43
  elif isinstance(value, int) or isinstance(value, float):
44
- nanos = value * 1e9
44
+ nanos = round(value * 1e9)
45
45
  obj.FromNanoseconds(nanos)
46
46
  elif isinstance(value, str):
47
47
  if re.match(r"-?\d+(?:.\d+)?s", value):
@@ -15,7 +15,7 @@
15
15
 
16
16
  from holado_test.scenario.step_tools import StepTools
17
17
  from holado.common.context.session_context import SessionContext
18
- from holado_test.behave.behave import *
18
+ from holado_test.behave.behave import * # @UnusedWildImport
19
19
  from holado_core.common.exceptions.functional_exception import FunctionalException
20
20
  from holado_protobuf.ipc.protobuf.protobuf_messages import ProtobufMessages
21
21
  import logging
@@ -26,6 +26,7 @@ from datetime import timedelta
26
26
  from dateutil.relativedelta import relativedelta
27
27
  import math
28
28
  from decimal import Decimal
29
+ from holado_python.standard_library.typing import Typing
29
30
 
30
31
  logger = logging.getLogger(__name__)
31
32
 
@@ -35,6 +36,7 @@ EPOCH_JULIAN_CNES = datetime.datetime(year=1950, month=1, day=1, tzinfo=datetime
35
36
  EPOCH_JULIAN_NASA = datetime.datetime(year=1968, month=5, day=24, tzinfo=datetime.timezone.utc)
36
37
 
37
38
  FORMAT_DATETIME_ISO = '%Y-%m-%dT%H:%M:%S.%fZ'
39
+ FORMAT_DATETIME_ISO_SECONDS = '%Y-%m-%dT%H:%M:%SZ'
38
40
  FORMAT_DATETIME_HUMAN_SECOND = '%Y-%m-%d %H:%M:%S'
39
41
 
40
42
 
@@ -159,12 +161,24 @@ class DateTime(object):
159
161
 
160
162
  ### Conversions to/from timestamp
161
163
 
164
+ @classmethod
165
+ def _get_epoch_datetime(cls, epoch):
166
+ res = None
167
+ if epoch is not None:
168
+ if isinstance(epoch, str):
169
+ if epoch in ['EPOCH_1970', 'EPOCH_JULIAN_CNES', 'EPOCH_JULIAN_NASA']:
170
+ res = eval(epoch)
171
+ else:
172
+ res = DateTime.str_2_datetime(epoch)
173
+ elif isinstance(epoch, datetime.datetime):
174
+ res = epoch
175
+ else:
176
+ raise TechnicalException(f"Epoch can be a datetime, a str containing a datetime, a str containing an EPOCH name, or None")
177
+ return res
178
+
162
179
  @classmethod
163
180
  def datetime_to_timestamp(cls, dt, epoch=None):
164
- if epoch is not None and isinstance(epoch, str):
165
- epoch = DateTime.str_2_datetime(epoch)
166
- elif epoch is not None and not isinstance(epoch, datetime.datetime):
167
- raise TechnicalException(f"Parameter 'epoch' should be a datetime or None")
181
+ epoch = cls._get_epoch_datetime(epoch)
168
182
 
169
183
  if epoch is None:
170
184
  return dt.timestamp()
@@ -176,12 +190,17 @@ class DateTime(object):
176
190
  """
177
191
  Convert timestamp to UTC datetime
178
192
  """
179
- if epoch is not None and isinstance(epoch, str):
180
- epoch = DateTime.str_2_datetime(epoch)
181
- elif epoch is not None and not isinstance(epoch, datetime.datetime):
182
- raise TechnicalException(f"Parameter 'epoch' should be a datetime or None")
193
+ epoch = cls._get_epoch_datetime(epoch)
194
+
195
+ if isinstance(ts, str):
196
+ if Converter.is_float(ts) or Converter.is_integer(ts):
197
+ ts = Converter.to_float(ts)
198
+ else:
199
+ raise FunctionalException(f"Timestamp is a string that doesn't contain a float|int (obtained timestamp: '{ts}')")
200
+ if not isinstance(ts, float) and not isinstance(ts, int):
201
+ raise TechnicalException(f"Timestamp must be a float|int or a string containing a float|int (obtained type: {Typing.get_object_class_fullname(ts)})")
183
202
 
184
- res = datetime.datetime.utcfromtimestamp(ts)
203
+ res = datetime.datetime.fromtimestamp(ts, tz=datetime.timezone.utc)
185
204
  if epoch is not None:
186
205
  res = epoch + (res - EPOCH_1970)
187
206
  return res
@@ -212,9 +231,9 @@ class DateTime(object):
212
231
  """
213
232
  Convert (seconds, nanos) to timestamp.
214
233
  By default, it returns a timestamp as float with nanos rounded to microsecond precision.
215
- To keep nanosecond precision, set cast_type=None and round_func=None, it will returns a timestamp as decimal.Decimal.
216
- @param cast_func: Type of the returned value (default: float). The type can also be decimal.Decimal, usefull to keep nanosecond precision.
217
- @param round_func: defines the rounding method applied to microseconds (default: round ; possible methods: round, math.ceil, math.trunc)
234
+ To keep nanosecond precision, set cast_func=None and round_func=None, it will returns a timestamp as decimal.Decimal.
235
+ @param cast_func: Type of the returned value (default: float). If None, no cast is applied, and result is of type decimal.Decimal, usefull to keep nanosecond precision.
236
+ @param round_func: defines the rounding method applied to microseconds (default: round ; possible methods: round, math.ceil, math.trunc). If None, value is not rounded.
218
237
  """
219
238
  res_dec = Decimal(seconds) + Decimal(nanos) / Decimal(1e9)
220
239
  if round_func is not None:
@@ -13,9 +13,11 @@
13
13
 
14
14
  import logging
15
15
  import socket
16
- from holado_python.standard_library.socket.socket import SocketClient, SocketServer
16
+ from holado_python.standard_library.socket.socket import SocketClient, SocketServer, Socket
17
17
  import abc
18
18
  from holado_multitask.multithreading.loopfunctionthreaded import LoopFunctionThreaded
19
+ import time
20
+ from holado.common.handlers.undefined import undefined_argument
19
21
 
20
22
  logger = logging.getLogger(__name__)
21
23
 
@@ -31,8 +33,66 @@ class BlockingSocketClient(SocketClient):
31
33
  """
32
34
  __metaclass__ = abc.ABCMeta
33
35
 
34
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
35
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
36
+ 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):
37
+ 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)
38
+
39
+ def start(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
40
+ """Start client event loop.
41
+ """
42
+ if self.is_with_ssl:
43
+ # Set a small timeout, so that recv has a behaviour similar flag MSG_DONTWAIT
44
+ # See _process_recv_send implementation, recv flags are needed to be forced to 0
45
+ if self._idle_sleep_delay is not None:
46
+ self.internal_socket.settimeout(self._idle_sleep_delay)
47
+ else:
48
+ self.internal_socket.settimeout(0.01)
49
+
50
+ kwargs = {'read_bufsize':read_bufsize, 'read_kwargs':read_kwargs, 'write_kwargs':write_kwargs}
51
+ thread = LoopFunctionThreaded(self._process_recv_send, kwargs=kwargs, register_thread=True, delay_before_run_sec=None)
52
+ self._start_thread(thread)
53
+
54
+ def _process_recv_send(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
55
+ has_activity = False
56
+ read_kwargs = read_kwargs if read_kwargs is not None else {}
57
+ write_kwargs = write_kwargs if write_kwargs is not None else {}
58
+
59
+ recv_data = None
60
+ if self.is_run_with_recv:
61
+ if self.is_with_ssl:
62
+ # ssl doesn't suppôrt flags != 0
63
+ flags = 0
64
+ else:
65
+ # Add flag to not wait data
66
+ flags = read_kwargs.get('flags', 0)
67
+ flags |= socket.MSG_DONTWAIT
68
+
69
+ try:
70
+ recv_data = self.internal_socket.recv(read_bufsize, flags)
71
+ except (BlockingIOError, TimeoutError):
72
+ # No data to read
73
+ pass
74
+ else:
75
+ if recv_data:
76
+ has_activity = True
77
+ with self._data_lock:
78
+ self._data.in_bytes += recv_data
79
+ if logger.isEnabledFor(logging.DEBUG):
80
+ logger.debug(f"[{self.name}] Received [{recv_data}] (type: {type(recv_data)} ; total: {len(self._data.in_bytes)})")
81
+
82
+ sent = None
83
+ if self.is_run_with_send:
84
+ with self._data_lock:
85
+ if self._data.out_bytes:
86
+ has_activity = True
87
+ sent = self.internal_socket.send(self._data.out_bytes)
88
+ if sent > 0:
89
+ self._data.out_bytes = self._data.out_bytes[sent:]
90
+ if logger.isEnabledFor(logging.DEBUG):
91
+ logger.debug(f"[{self.name}] Sent {sent} data (remaining to send: {len(self._data.out_bytes)})")
92
+
93
+ # Wait before next loop if no data was exchanged
94
+ if not has_activity and self._idle_sleep_delay is not None:
95
+ time.sleep(self._idle_sleep_delay)
36
96
 
37
97
 
38
98
  class TCPBlockingSocketClient(BlockingSocketClient):
@@ -40,18 +100,27 @@ class TCPBlockingSocketClient(BlockingSocketClient):
40
100
  TCP socket client.
41
101
  """
42
102
 
43
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
44
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
103
+ 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):
104
+ 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)
45
105
 
46
106
  def create_ipv4_socket(self, host, port, **kwargs):
47
- ssl_context, kwargs = self._new_ssl_context_if_required(**kwargs)
48
-
49
- sock = socket.create_connection((host, port), **kwargs)
107
+ socket_kwargs = self._new_ssl_context_if_required(**kwargs)
50
108
 
51
- if ssl_context:
52
- sock = ssl_context.wrap_socket(sock, server_hostname=host, do_handshake_on_connect=False)
53
- # sock = ssl_context.wrap_socket(sock, server_hostname=host)
54
- self._set_internal_socket(sock)
109
+ if self.is_with_ssl:
110
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
111
+ # sock = Socket.create_connection((host, port), do_connect=False, **socket_kwargs)
112
+ # sock = Socket.create_connection((host, port), do_connect=True, **socket_kwargs)
113
+
114
+ wrap_socket_kwargs = self._ssl_wrap_socket_kwargs
115
+ # do_handshake_on_connect = wrap_socket_kwargs.pop('do_handshake_on_connect', True)
116
+ do_handshake_on_connect = wrap_socket_kwargs.pop('do_handshake_on_connect', False)
117
+ sock = self._ssl_context.wrap_socket(sock, server_hostname=host, do_handshake_on_connect=do_handshake_on_connect, **wrap_socket_kwargs)
118
+ self._set_internal_socket(sock, is_ssl_handshake_done_on_connect=do_handshake_on_connect)
119
+
120
+ sock.connect((host, port))
121
+ else:
122
+ sock = socket.create_connection((host, port), **socket_kwargs)
123
+ self._set_internal_socket(sock)
55
124
 
56
125
 
57
126
 
@@ -67,24 +136,16 @@ class BlockingSocketServer(SocketServer):
67
136
  """
68
137
  __metaclass__ = abc.ABCMeta
69
138
 
70
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
71
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
72
-
73
- self.__start_thread = None
139
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
140
+ super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
74
141
 
75
142
  def start(self, read_bufsize=1024, *, read_kwargs=None, write_kwargs=None):
76
143
  """Start server to wait a connection, and then listen data, process data, and send result.
77
144
  Note: current implementation is simple and supports only one connection at a time.
78
145
  """
79
146
  kwargs = {'read_bufsize':read_bufsize, 'read_kwargs':read_kwargs, 'write_kwargs':write_kwargs}
80
- self.__start_thread = LoopFunctionThreaded(self.wait_and_process_connection, kwargs=kwargs, register_thread=True, delay_before_run_sec=None)
81
- self.__start_thread.start()
82
-
83
- def stop(self):
84
- if self.__start_thread is not None:
85
- self.__start_thread.interrupt()
86
- self.__start_thread.join()
87
- self.__start_thread = None
147
+ thread = LoopFunctionThreaded(self.wait_and_process_connection, kwargs=kwargs, register_thread=True, delay_before_run_sec=None)
148
+ self._start_thread(thread)
88
149
 
89
150
  def wait_and_process_connection(self, read_bufsize=1024, *, read_kwargs=None, write_kwargs=None):
90
151
  """Wait a connection, and then listen data, process data, and send result.
@@ -93,26 +154,32 @@ class BlockingSocketServer(SocketServer):
93
154
  read_kwargs = read_kwargs if read_kwargs is not None else {}
94
155
  write_kwargs = write_kwargs if write_kwargs is not None else {}
95
156
 
96
- conn, _ = self.accept()
157
+ conn, from_addr = self.accept()
97
158
  if logger.isEnabledFor(logging.DEBUG):
98
- logger.debug(f"[{self.name}] New connection: {conn}")
159
+ logger.debug(f"[{self.name}] New connection: {conn} from {from_addr}")
160
+
161
+ if self.is_with_ssl:
162
+ wrap_socket_kwargs = self._ssl_wrap_socket_kwargs
163
+ do_handshake_on_connect = wrap_socket_kwargs.pop('do_handshake_on_connect', False)
164
+ # do_handshake_on_connect = wrap_socket_kwargs.pop('do_handshake_on_connect', True)
165
+ ssocket = self._ssl_context.wrap_socket(conn.internal_socket, server_side=True, do_handshake_on_connect=do_handshake_on_connect)
166
+ conn = Socket.create(name=conn.name, internal_socket=ssocket)
167
+
99
168
  with conn:
169
+ if logger.isEnabledFor(logging.DEBUG):
170
+ logger.debug(f"[{self.name} - {from_addr}] Start read & write loop")
100
171
  while True:
101
172
  data = conn.read(read_bufsize, **read_kwargs)
102
173
  if logger.isEnabledFor(logging.DEBUG):
103
- logger.debug(f"[{self.name}] Received: {data}")
174
+ logger.debug(f"[{self.name} - {from_addr}] Received: {data}")
104
175
 
105
176
  if not data:
106
177
  break
107
- result = self._process_data(data)
178
+ result = self._process_received_data(data)
108
179
 
109
180
  if logger.isEnabledFor(logging.DEBUG):
110
- logger.debug(f"[{self.name}] Sending: {result}")
181
+ logger.debug(f"[{self.name} - {from_addr}] Sending: {result}")
111
182
  conn.write(result, **write_kwargs)
112
-
113
- @abc.abstractmethod
114
- def _process_data(self, data):
115
- raise NotImplementedError()
116
183
 
117
184
 
118
185
 
@@ -121,16 +188,19 @@ class TCPBlockingSocketServer(BlockingSocketServer):
121
188
  TCP socket server
122
189
  """
123
190
 
124
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
125
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
191
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
192
+ super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
126
193
 
127
194
  def create_ipv4_socket(self, host, port, **kwargs):
128
- ssl_context, kwargs = self._new_ssl_context_if_required(server_side=True, **kwargs)
129
-
130
- sock = socket.create_server((host, port), **kwargs)
195
+ socket_kwargs = self._new_ssl_context_if_required(server_side=True, **kwargs)
131
196
 
132
- if ssl_context:
133
- # sock = ssl_context.wrap_socket(sock, do_handshake_on_connect=False, server_side=True)
134
- sock = ssl_context.wrap_socket(sock, server_side=True)
135
- self._set_internal_socket(sock)
197
+ if self.is_with_ssl:
198
+ # sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
199
+ # sock.bind((host, port))
200
+ # sock.listen()
201
+ sock = socket.create_server((host, port), **socket_kwargs)
202
+ self._set_internal_socket(sock)
203
+ else:
204
+ sock = socket.create_server((host, port), **socket_kwargs)
205
+ self._set_internal_socket(sock)
136
206
 
@@ -13,16 +13,17 @@
13
13
 
14
14
  import logging
15
15
  from holado_python.standard_library.socket.blocking_socket import TCPBlockingSocketServer
16
+ from holado.common.handlers.undefined import undefined_argument
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
19
20
 
20
21
 
21
22
  class EchoTCPBlockingSocketServer(TCPBlockingSocketServer):
22
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
23
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
23
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
24
+ super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
24
25
 
25
- def _process_data(self, data):
26
+ def _process_received_data(self, data):
26
27
  return data
27
28
 
28
29