holado 0.8.4__py3-none-any.whl → 0.9.1__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/common/context/session_context.py +6 -5
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/METADATA +1 -1
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/RECORD +56 -41
- holado_core/common/block/base.py +3 -3
- holado_core/common/resource/persisted_data_manager.py +10 -10
- holado_core/common/resource/resource_manager.py +34 -37
- holado_core/common/resource/table_data_manager.py +110 -0
- holado_core/common/tools/path_manager.py +31 -6
- holado_django/server/django_projects/rest_api/rest_api/urls.py +1 -1
- holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +2 -2
- holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +38 -1
- holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +3 -3
- holado_examples/tests/behave/testing_solution/environment.py +3 -3
- holado_examples/tests/behave/testing_solution/src/context/session_context.py +2 -2
- holado_examples/tests/behave/testing_solution/steps/config_steps.py +1 -1
- holado_multitask/multithreading/loopthread.py +4 -4
- holado_python/common/tools/datetime.py +37 -16
- holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
- holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
- holado_python/tests/behave/steps/standard_library/datetime_steps.py +2 -2
- holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +3 -3
- holado_report/__init__.py +1 -0
- holado_report/campaign/campaign_manager.py +194 -0
- holado_report/report/builders/detailed_scenario_failed_report_builder.py +11 -7
- holado_report/report/builders/failure_report_builder.py +4 -3
- holado_report/report/builders/short_scenario_failed_report_builder.py +6 -5
- holado_report/report/builders/summary_report_builder.py +1 -1
- holado_report/report/builders/summary_scenario_failed_report_builder.py +6 -5
- holado_report/report/builders/summary_scenario_report_builder.py +12 -11
- holado_report/report/execution_historic.py +4 -2
- holado_report/report/report_manager.py +65 -13
- holado_rest/api/rest/rest_client.py +5 -1
- holado_system/system/filesystem/file.py +5 -2
- holado_test/__init__.py +3 -0
- holado_test/common/context/feature_context.py +3 -3
- holado_test/common/context/scenario_context.py +3 -3
- holado_test/common/context/step_context.py +3 -3
- holado_test/test_server/client/rest/test_server_client.py +117 -0
- holado_test/test_server/server/Dockerfile +70 -0
- holado_test/test_server/server/core/server_context.py +42 -0
- holado_test/test_server/server/core/server_manager.py +54 -0
- holado_test/test_server/server/requirements.txt +2 -0
- holado_test/test_server/server/rest/README +2 -0
- holado_test/test_server/server/rest/api/__init__.py +23 -0
- holado_docker/tools/docker_controller/docker_controller_manager.py → holado_test/test_server/server/rest/api/campaign/__init__.py +10 -28
- holado_test/test_server/server/rest/api/campaign/scenario.py +33 -0
- holado_test/test_server/server/rest/initialize_holado.py +72 -0
- holado_test/test_server/server/rest/logging.conf +50 -0
- holado_test/test_server/server/rest/openapi.yaml +46 -0
- holado_test/test_server/server/rest/run.py +40 -0
- holado_test/test_server/server/run_test_server_in_docker.sh +101 -0
- holado_value/common/tools/unique_value_manager.py +2 -1
- test_holado/environment.py +1 -1
- test_holado/logging.conf +1 -0
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/WHEEL +0 -0
- {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
import logging
|
|
15
|
+
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
16
|
+
from holado_core.common.tools.tools import Tools
|
|
17
|
+
import copy
|
|
18
|
+
from holado.common.handlers.undefined import undefined_argument
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TableDataManager():
|
|
25
|
+
"""
|
|
26
|
+
Manage data stored in a dedicated resource table.
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self, data_name, table_name, table_sql_create, db_name="default", is_persistent=False, do_audit=False):
|
|
29
|
+
self.__data_name = data_name
|
|
30
|
+
self.__table_name = table_name
|
|
31
|
+
self.__table_sql_create = table_sql_create
|
|
32
|
+
self.__db_name = db_name
|
|
33
|
+
self.__is_persistent = is_persistent
|
|
34
|
+
self.__do_audit = do_audit
|
|
35
|
+
|
|
36
|
+
self.__resource_manager = None
|
|
37
|
+
|
|
38
|
+
def initialize(self, resource_manager):
|
|
39
|
+
self.__resource_manager = resource_manager
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def table_name(self):
|
|
43
|
+
return self.__table_name
|
|
44
|
+
|
|
45
|
+
def ensure_db_exists(self):
|
|
46
|
+
if self.__resource_manager.has_data_table(self.__table_name, db_name=self.__db_name, is_persistent=self.__is_persistent):
|
|
47
|
+
if self.__resource_manager.check_data_table_schema(self.__table_name, self.__table_sql_create, db_name=self.__db_name, is_persistent=self.__is_persistent):
|
|
48
|
+
# Table already exists with the right schema
|
|
49
|
+
return
|
|
50
|
+
else:
|
|
51
|
+
# Table already exists but with wrong schema => delete table before creating it again
|
|
52
|
+
self.__resource_manager.delete_data_table(self.__table_name, db_name=self.__db_name, is_persistent=self.__is_persistent, raise_if_not_exist=True, do_commit=True)
|
|
53
|
+
|
|
54
|
+
# Create table
|
|
55
|
+
# Note: method create_data_table raise an exception if it doesn't succeed to create the table
|
|
56
|
+
self.__resource_manager.create_data_table(self.__table_name, self.__table_sql_create, db_name=self.__db_name, is_persistent=self.__is_persistent, raise_if_exist=True, do_commit=True, do_audit=self.__do_audit)
|
|
57
|
+
|
|
58
|
+
def has_data(self, filter_data=None, filter_compare_data=None):
|
|
59
|
+
return self.__resource_manager.has_data(self.__table_name, where_data=filter_data, where_compare_data=filter_compare_data, db_name=self.__db_name, is_persistent=self.__is_persistent)
|
|
60
|
+
|
|
61
|
+
def get_datas(self, filter_data=None, filter_compare_data=None, as_generator=False):
|
|
62
|
+
"""
|
|
63
|
+
Note: Whereas 'data' is already a plural, a 's' is added in method name to be coherent with other method names
|
|
64
|
+
"""
|
|
65
|
+
return self.__resource_manager.get_data(self.__table_name, where_data=filter_data, where_compare_data=filter_compare_data, db_name=self.__db_name, is_persistent=self.__is_persistent, result_as_dict_list=True, as_generator=as_generator)
|
|
66
|
+
|
|
67
|
+
def get_data(self, filter_data=None, filter_compare_data=None):
|
|
68
|
+
"""
|
|
69
|
+
Note: Whereas 'datum' should be the right word in method name since method returns only one datum, method is named with 'data' in its usual singular meaning for most people.
|
|
70
|
+
"""
|
|
71
|
+
data = self.get_datas(filter_data=filter_data, filter_compare_data=filter_compare_data)
|
|
72
|
+
if len(data) > 1:
|
|
73
|
+
raise TechnicalException(f"Too many ({len(data)}) {self.__data_name} found for filter {filter_data}.")
|
|
74
|
+
elif len(data) == 1:
|
|
75
|
+
return data[0]
|
|
76
|
+
else:
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def count_data(self, filter_data=None, filter_compare_data=None):
|
|
80
|
+
return self.__resource_manager.count_data(self.__table_name, where_data=filter_data, where_compare_data=filter_compare_data, db_name=self.__db_name, is_persistent=self.__is_persistent)
|
|
81
|
+
|
|
82
|
+
def update_data(self, data, filter_data=None, filter_compare_data=None):
|
|
83
|
+
if Tools.do_log(logger, logging.DEBUG):
|
|
84
|
+
logger.debug(f"Update {self.__data_name} for {filter_data} and {filter_compare_data}: {data}")
|
|
85
|
+
self.__resource_manager.update_data(self.__table_name, data, where_data=filter_data, where_compare_data=filter_compare_data, db_name=self.__db_name, is_persistent=self.__is_persistent)
|
|
86
|
+
|
|
87
|
+
def add_data(self, data, filter_data=None):
|
|
88
|
+
if Tools.do_log(logger, logging.DEBUG):
|
|
89
|
+
logger.debug(f"Add {self.__data_name} for {filter_data}: {data}")
|
|
90
|
+
data = copy.copy(data)
|
|
91
|
+
if filter_data:
|
|
92
|
+
data.update(filter_data)
|
|
93
|
+
self.__resource_manager.add_data(self.__table_name, data, db_name=self.__db_name, is_persistent=self.__is_persistent)
|
|
94
|
+
|
|
95
|
+
def update_or_add_data(self, filter_data, data, existing_data=undefined_argument):
|
|
96
|
+
if existing_data is undefined_argument:
|
|
97
|
+
has_data = self.has_data(filter_data)
|
|
98
|
+
else:
|
|
99
|
+
has_data = existing_data is not None
|
|
100
|
+
|
|
101
|
+
if has_data:
|
|
102
|
+
self.update_data(data, filter_data=filter_data)
|
|
103
|
+
else:
|
|
104
|
+
self.add_data(data, filter_data)
|
|
105
|
+
|
|
106
|
+
def delete_data(self, filter_data):
|
|
107
|
+
self.__resource_manager.delete_data(self.__table_name, where_data=filter_data, db_name=self.__db_name, is_persistent=self.__is_persistent)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
@@ -21,6 +21,7 @@ from holado_core.common.exceptions.functional_exception import FunctionalExcepti
|
|
|
21
21
|
from datetime import datetime
|
|
22
22
|
from holado_core.common.tools.tools import Tools
|
|
23
23
|
from pathlib import Path
|
|
24
|
+
from holado_python.common.tools.datetime import DateTime
|
|
24
25
|
|
|
25
26
|
logger = logging.getLogger(__name__)
|
|
26
27
|
|
|
@@ -188,19 +189,43 @@ class PathManager(object):
|
|
|
188
189
|
def get_timestamped_path(self, prefix, ext, dt=None, dt_format="%Y%m%d-%H%M%S"):
|
|
189
190
|
ext = ext.strip('.')
|
|
190
191
|
if dt is None:
|
|
191
|
-
dt =
|
|
192
|
+
dt = DateTime.now()
|
|
192
193
|
now_str = datetime.strftime(dt, dt_format)
|
|
193
194
|
return f"{prefix}_{now_str}.{ext}"
|
|
194
195
|
|
|
195
|
-
def
|
|
196
|
+
def find_files(self, dir_path, prefix=None, subdir_relative_path=None, since_datetime=None):
|
|
196
197
|
res = []
|
|
197
198
|
for filename in os.listdir(dir_path):
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
file_path = os.path.join(dir_path, filename)
|
|
200
|
+
|
|
201
|
+
if subdir_relative_path is not None:
|
|
202
|
+
# Filter directories that doesn't contain expected sub-file
|
|
203
|
+
if not os.path.isdir(file_path):
|
|
204
|
+
continue
|
|
205
|
+
file_path = os.path.join(file_path, subdir_relative_path)
|
|
206
|
+
if not os.path.exists(file_path):
|
|
207
|
+
continue
|
|
208
|
+
else:
|
|
209
|
+
# Filter paths that are not files
|
|
210
|
+
if not os.path.isfile(file_path):
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Filter on prefix
|
|
214
|
+
if prefix is not None and not filename.startswith(prefix):
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
# Filter on file modification datetime
|
|
218
|
+
if since_datetime is not None:
|
|
219
|
+
dt_last_modif = DateTime.timestamp_to_datetime(os.path.getmtime(file_path))
|
|
220
|
+
if dt_last_modif < since_datetime:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# File is matching criteria
|
|
224
|
+
res.append(file_path)
|
|
200
225
|
return res
|
|
201
226
|
|
|
202
|
-
def
|
|
203
|
-
files = self.
|
|
227
|
+
def find_file(self, dir_path, prefix=None, since_datetime=None):
|
|
228
|
+
files = self.find_files(dir_path, prefix=prefix, since_datetime=since_datetime)
|
|
204
229
|
|
|
205
230
|
if len(files) == 0:
|
|
206
231
|
raise FunctionalException(f"Unable to find a file starting with '{prefix}' in folder '{dir_path}'")
|
|
@@ -18,7 +18,7 @@ from django.contrib import admin
|
|
|
18
18
|
from django.urls import path
|
|
19
19
|
from rest_framework import routers
|
|
20
20
|
from django.urls.conf import include
|
|
21
|
-
from rest_api.application.apps import django_application_inst
|
|
21
|
+
from rest_api.application.apps import django_application_inst # @UnresolvedImport
|
|
22
22
|
from holado_core.common.exceptions.technical_exception import TechnicalException
|
|
23
23
|
|
|
24
24
|
router = routers.DefaultRouter()
|
|
@@ -19,7 +19,7 @@ import logging
|
|
|
19
19
|
from holado_test.scenario.step_tools import StepTools
|
|
20
20
|
from holado_test.behave.scenario.behave_step_tools import BehaveStepTools
|
|
21
21
|
from holado_value.common.tables.converters.value_table_converter import ValueTableConverter
|
|
22
|
-
from holado_docker.tools.docker_controller.
|
|
22
|
+
from holado_docker.tools.docker_controller.client.rest.docker_controller_client import DockerControllerClient
|
|
23
23
|
|
|
24
24
|
logger = logging.getLogger(__name__)
|
|
25
25
|
|
|
@@ -40,7 +40,7 @@ def step_impl(context, var_name):
|
|
|
40
40
|
else:
|
|
41
41
|
kwargs = {}
|
|
42
42
|
|
|
43
|
-
res =
|
|
43
|
+
res = DockerControllerClient.new_client(**kwargs)
|
|
44
44
|
|
|
45
45
|
__get_variable_manager().register_variable(var_name, res)
|
|
46
46
|
|
|
@@ -13,13 +13,50 @@
|
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
15
|
from holado_rest.api.rest.rest_client import RestClient
|
|
16
|
-
from holado.common.handlers.undefined import default_value
|
|
16
|
+
from holado.common.handlers.undefined import default_value, undefined_argument,\
|
|
17
|
+
undefined_value
|
|
18
|
+
import os
|
|
19
|
+
from holado_core.common.tools.converters.converter import Converter
|
|
20
|
+
from holado_rest.api.rest.rest_manager import RestManager
|
|
17
21
|
|
|
18
22
|
logger = logging.getLogger(__name__)
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
class DockerControllerClient(RestClient):
|
|
22
26
|
|
|
27
|
+
@classmethod
|
|
28
|
+
def new_client(cls, use_localhost=undefined_argument, **kwargs):
|
|
29
|
+
if 'name' not in kwargs:
|
|
30
|
+
kwargs['name'] = None
|
|
31
|
+
if 'url' not in kwargs:
|
|
32
|
+
if use_localhost is undefined_argument:
|
|
33
|
+
env_use = os.getenv("HOLADO_USE_LOCALHOST", False)
|
|
34
|
+
use_localhost = Converter.is_boolean(env_use) and Converter.to_boolean(env_use)
|
|
35
|
+
|
|
36
|
+
url = os.getenv("HOLADO_DOCKER_CONTROLLER_URL", undefined_value)
|
|
37
|
+
if url is undefined_value:
|
|
38
|
+
scheme = kwargs.get('scheme', undefined_value)
|
|
39
|
+
if scheme is undefined_value:
|
|
40
|
+
scheme = os.getenv("HOLADO_DOCKER_CONTROLLER_SCHEME", "http")
|
|
41
|
+
host = kwargs.get('host', undefined_value)
|
|
42
|
+
if host is undefined_value:
|
|
43
|
+
host = "localhost" if use_localhost else os.getenv("HOLADO_DOCKER_CONTROLLER_HOST", "holado_docker_controller")
|
|
44
|
+
port = kwargs.get('port', undefined_value)
|
|
45
|
+
if port is undefined_value:
|
|
46
|
+
port = os.getenv("HOLADO_DOCKER_CONTROLLER_PORT", 51231)
|
|
47
|
+
|
|
48
|
+
if port is None:
|
|
49
|
+
url = f"{scheme}://{host}"
|
|
50
|
+
else:
|
|
51
|
+
url = f"{scheme}://{host}:{port}"
|
|
52
|
+
kwargs['url'] = url
|
|
53
|
+
|
|
54
|
+
manager = RestManager(default_client_class=DockerControllerClient)
|
|
55
|
+
res = manager.new_client(**kwargs)
|
|
56
|
+
|
|
57
|
+
return res
|
|
58
|
+
|
|
59
|
+
|
|
23
60
|
def __init__(self, name, url, headers=None):
|
|
24
61
|
super().__init__(name, url, headers)
|
|
25
62
|
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
# Have access to any HolAdo registry.
|
|
13
13
|
#
|
|
14
14
|
# Optionally, define in .profile the following variables
|
|
15
|
-
# -
|
|
16
|
-
# - HOLADO_DOCKER_CONTROLLER_PORT: REST API port to use (default:
|
|
15
|
+
# - HOLADO_DOCKER_CONTROLLER_HOST: host name or name of the container
|
|
16
|
+
# - HOLADO_DOCKER_CONTROLLER_PORT: REST API port to use (default: 51231)
|
|
17
17
|
# - HOLADO_IMAGE_REGISTRY: docker image registry to use (default: holado/docker_controller)
|
|
18
18
|
# - HOLADO_IMAGE_TAG: docker image tag to use (default: latest)
|
|
19
19
|
# - HOLADO_OUTPUT_BASEDIR: absolute path to base output directory (default: [HOME]/.holado/output)
|
|
@@ -75,7 +75,7 @@ fi
|
|
|
75
75
|
|
|
76
76
|
# Define port to use
|
|
77
77
|
if [[ -z "$HOLADO_DOCKER_CONTROLLER_PORT" ]]; then
|
|
78
|
-
HOLADO_DOCKER_CONTROLLER_PORT=
|
|
78
|
+
HOLADO_DOCKER_CONTROLLER_PORT=51231
|
|
79
79
|
fi
|
|
80
80
|
|
|
81
81
|
|
|
@@ -12,13 +12,13 @@ source_path = os.path.normpath(os.path.join(here, 'src'))
|
|
|
12
12
|
sys.path.insert(0, source_path)
|
|
13
13
|
|
|
14
14
|
# Add HolAdo source paths (needed when using a clone of HolAdo project)
|
|
15
|
-
from initialize_holado import insert_holado_source_paths
|
|
15
|
+
from initialize_holado import insert_holado_source_paths # @UnresolvedImport
|
|
16
16
|
insert_holado_source_paths()
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Configure HolAdo
|
|
20
20
|
import holado
|
|
21
|
-
from context.session_context import TSSessionContext
|
|
21
|
+
from context.session_context import TSSessionContext # @UnresolvedImport
|
|
22
22
|
# holado.initialize(TSessionContext=TSSessionContext,
|
|
23
23
|
holado.initialize(TSessionContext=None,
|
|
24
24
|
logging_config_file_path=os.path.join(here, 'logging.conf'), log_level=logging.INFO,
|
|
@@ -26,7 +26,7 @@ holado.initialize(TSessionContext=None,
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# Import generic environment methods
|
|
29
|
-
from behave_environment import *
|
|
29
|
+
from behave_environment import * # @UnresolvedImport
|
|
30
30
|
|
|
31
31
|
# Define project specific environment methods
|
|
32
32
|
# TestConfig.profile_memory_in_features = True
|
|
@@ -17,7 +17,7 @@ class TSSessionContext(SessionContext):
|
|
|
17
17
|
|
|
18
18
|
# Override default registered modules
|
|
19
19
|
|
|
20
|
-
from common.tools.path_manager import TSPathManager
|
|
20
|
+
from common.tools.path_manager import TSPathManager # @UnresolvedImport
|
|
21
21
|
self.services.register_service_type("path_manager", TSPathManager,
|
|
22
22
|
lambda m: m.initialize(),
|
|
23
23
|
raise_if_exist=False )
|
|
@@ -25,7 +25,7 @@ class TSSessionContext(SessionContext):
|
|
|
25
25
|
|
|
26
26
|
# Register new modules
|
|
27
27
|
|
|
28
|
-
from config.config_manager import TSConfigManager
|
|
28
|
+
from config.config_manager import TSConfigManager # @UnresolvedImport
|
|
29
29
|
self.services.register_service_type("config_manager", TSConfigManager,
|
|
30
30
|
lambda m: m.initialize(lambda: self.path_manager) )
|
|
31
31
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from holado_test.behave.behave import * # @UnusedWildImport
|
|
5
5
|
from holado.common.context.session_context import SessionContext
|
|
6
|
-
from config.config_manager import TSConfigManager
|
|
6
|
+
from config.config_manager import TSConfigManager # @UnresolvedImport
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
logger = logging.getLogger(__name__)
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
|
|
14
14
|
import threading
|
|
15
15
|
import time
|
|
16
|
-
import datetime
|
|
17
16
|
import logging
|
|
18
17
|
import holado_multitask.multithreading.thread as ctt
|
|
19
18
|
from holado.common.handlers.undefined import default_context
|
|
19
|
+
from holado_python.common.tools.datetime import DateTime
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
@@ -31,7 +31,7 @@ class LoopThread(ctt.InterruptableThread):
|
|
|
31
31
|
|
|
32
32
|
# Manage loops
|
|
33
33
|
self.__is_under_loop = threading.Event()
|
|
34
|
-
self.__next_loop_datetime =
|
|
34
|
+
self.__next_loop_datetime = DateTime.now()
|
|
35
35
|
self.__loop_counter = 0
|
|
36
36
|
|
|
37
37
|
@property
|
|
@@ -79,10 +79,10 @@ class LoopThread(ctt.InterruptableThread):
|
|
|
79
79
|
logger.info("Loop thread '{}' has finished.".format(self.name))
|
|
80
80
|
|
|
81
81
|
def does_need_run_loop(self):
|
|
82
|
-
return
|
|
82
|
+
return DateTime.now() >= self.__next_loop_datetime
|
|
83
83
|
|
|
84
84
|
def _get_sleep_time_before_next_loop(self):
|
|
85
|
-
now =
|
|
85
|
+
now = DateTime.now()
|
|
86
86
|
if now < self.__next_loop_datetime:
|
|
87
87
|
delta = self.__next_loop_datetime - now
|
|
88
88
|
res = delta.total_seconds()
|
|
@@ -39,6 +39,9 @@ FORMAT_DATETIME_ISO = '%Y-%m-%dT%H:%M:%S.%fZ'
|
|
|
39
39
|
FORMAT_DATETIME_ISO_SECONDS = '%Y-%m-%dT%H:%M:%SZ'
|
|
40
40
|
FORMAT_DATETIME_HUMAN_SECOND = '%Y-%m-%d %H:%M:%S'
|
|
41
41
|
|
|
42
|
+
TIMEZONE_LOCAL = datetime.datetime.now().astimezone().tzinfo
|
|
43
|
+
TIMEZONE_UTC = datetime.timezone.utc
|
|
44
|
+
|
|
42
45
|
|
|
43
46
|
class DurationUnit(str, Enum):
|
|
44
47
|
# Nanosecond = "nanosecond"
|
|
@@ -55,11 +58,16 @@ class DurationUnit(str, Enum):
|
|
|
55
58
|
|
|
56
59
|
|
|
57
60
|
class DateTime(object):
|
|
61
|
+
""" Tools to manipulate datetimes.
|
|
62
|
+
Note: Naive and aware datetimes cannot be compared. Thus HolAdo methods always set datetime timezone when it is known.
|
|
63
|
+
In methods unable to define the timezone (ex: str_2_datetime), it is highly recommended to specify 'tz' argument.
|
|
64
|
+
"""
|
|
58
65
|
|
|
59
66
|
__is_system_time_in_tai = None
|
|
60
67
|
__is_system_time_expected_in_tai = None
|
|
61
68
|
__utc_to_tai_timedelta = None
|
|
62
69
|
|
|
70
|
+
|
|
63
71
|
def __init__(self):
|
|
64
72
|
pass
|
|
65
73
|
|
|
@@ -71,22 +79,22 @@ class DateTime(object):
|
|
|
71
79
|
return cls.__is_system_time_in_tai
|
|
72
80
|
|
|
73
81
|
@classmethod
|
|
74
|
-
def now(cls):
|
|
75
|
-
return datetime.datetime.now()
|
|
82
|
+
def now(cls, tz=TIMEZONE_LOCAL):
|
|
83
|
+
return datetime.datetime.now(tz=tz)
|
|
76
84
|
|
|
77
85
|
@classmethod
|
|
78
86
|
def utcnow(cls):
|
|
79
87
|
if cls.is_system_time_in_tai():
|
|
80
|
-
return
|
|
88
|
+
return cls.now(TIMEZONE_UTC) - cls.utc_to_tai_timedelta()
|
|
81
89
|
else:
|
|
82
|
-
return
|
|
90
|
+
return cls.now(TIMEZONE_UTC)
|
|
83
91
|
|
|
84
92
|
@classmethod
|
|
85
93
|
def tainow(cls):
|
|
86
94
|
if cls.is_system_time_in_tai():
|
|
87
|
-
return
|
|
95
|
+
return cls.now(TIMEZONE_UTC)
|
|
88
96
|
else:
|
|
89
|
-
return
|
|
97
|
+
return cls.now(TIMEZONE_UTC) + cls.utc_to_tai_timedelta()
|
|
90
98
|
|
|
91
99
|
@classmethod
|
|
92
100
|
def utc_to_tai_timedelta(cls):
|
|
@@ -126,15 +134,28 @@ class DateTime(object):
|
|
|
126
134
|
return res
|
|
127
135
|
|
|
128
136
|
@classmethod
|
|
129
|
-
def str_2_datetime(cls, dt_str, dt_format=None):
|
|
130
|
-
"""Convert string to datetime instance
|
|
137
|
+
def str_2_datetime(cls, dt_str, dt_format=None, tz=None):
|
|
138
|
+
"""Convert string to datetime instance
|
|
139
|
+
@param dt_format If defined, use this format to parse string rather than using first matching format
|
|
140
|
+
@param tz If defined, return a datetime with this timezone.
|
|
141
|
+
If string already defines a timezone, the datetime is converted to this timezone,
|
|
142
|
+
else the string is supposed to represent a datetime in this timezone and timezone is set in result datetime.
|
|
143
|
+
Note: Naive and aware datetimes cannot be compared. Thus HolAdo methods always set datetime timezone when it is known.
|
|
144
|
+
It is highly recommended to specify 'tz' argument
|
|
145
|
+
"""
|
|
131
146
|
res = None
|
|
132
147
|
if dt_format is None:
|
|
133
148
|
res = dateutil.parser.parse(dt_str)
|
|
134
149
|
else:
|
|
135
150
|
res = datetime.datetime.strptime(dt_str, dt_format)
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
|
|
152
|
+
# Set timezone if needed
|
|
153
|
+
if tz is not None:
|
|
154
|
+
if res.tzinfo is None:
|
|
155
|
+
res = res.replace(tzinfo=tz)
|
|
156
|
+
else:
|
|
157
|
+
res = res.astimezone(tz)
|
|
158
|
+
|
|
138
159
|
return res
|
|
139
160
|
|
|
140
161
|
@classmethod
|
|
@@ -159,19 +180,19 @@ class DateTime(object):
|
|
|
159
180
|
return res
|
|
160
181
|
|
|
161
182
|
@classmethod
|
|
162
|
-
def _get_datetime(cls, dt):
|
|
183
|
+
def _get_datetime(cls, dt, tz_in_str=None):
|
|
163
184
|
res = None
|
|
164
185
|
if dt is not None:
|
|
165
186
|
if isinstance(dt, datetime.datetime):
|
|
166
187
|
res = dt
|
|
167
188
|
elif isinstance(dt, str):
|
|
168
|
-
res = DateTime.str_2_datetime(dt)
|
|
189
|
+
res = DateTime.str_2_datetime(dt, tz=tz_in_str)
|
|
169
190
|
else:
|
|
170
191
|
raise TechnicalException(f"Datetime can be a datetime, a str containing a datetime, or None")
|
|
171
192
|
return res
|
|
172
193
|
|
|
173
194
|
@classmethod
|
|
174
|
-
def _get_epoch_datetime(cls, epoch):
|
|
195
|
+
def _get_epoch_datetime(cls, epoch, tz_in_str=None):
|
|
175
196
|
res = None
|
|
176
197
|
if epoch is not None:
|
|
177
198
|
if isinstance(epoch, datetime.datetime):
|
|
@@ -180,7 +201,7 @@ class DateTime(object):
|
|
|
180
201
|
if epoch in ['EPOCH_1970', 'EPOCH_JULIAN_CNES', 'EPOCH_JULIAN_NASA']:
|
|
181
202
|
res = eval(epoch)
|
|
182
203
|
else:
|
|
183
|
-
res = DateTime.str_2_datetime(epoch)
|
|
204
|
+
res = DateTime.str_2_datetime(epoch, tz=tz_in_str)
|
|
184
205
|
else:
|
|
185
206
|
raise TechnicalException(f"Epoch can be a datetime, a str containing a datetime, a str containing an EPOCH name, or None")
|
|
186
207
|
return res
|
|
@@ -293,7 +314,7 @@ class DateTime(object):
|
|
|
293
314
|
"""
|
|
294
315
|
dt_src = dt
|
|
295
316
|
if isinstance(dt_src, str):
|
|
296
|
-
dt_src = cls.str_2_datetime(dt_src, dt_format)
|
|
317
|
+
dt_src = cls.str_2_datetime(dt_src, dt_format, tz=TIMEZONE_UTC)
|
|
297
318
|
|
|
298
319
|
return dt_src + DateTime.utc_to_tai_timedelta()
|
|
299
320
|
|
|
@@ -305,7 +326,7 @@ class DateTime(object):
|
|
|
305
326
|
"""
|
|
306
327
|
dt_src = dt
|
|
307
328
|
if isinstance(dt_src, str):
|
|
308
|
-
dt_src = cls.str_2_datetime(dt_src, dt_format)
|
|
329
|
+
dt_src = cls.str_2_datetime(dt_src, dt_format, tz=TIMEZONE_UTC)
|
|
309
330
|
|
|
310
331
|
return dt_src - DateTime.utc_to_tai_timedelta()
|
|
311
332
|
|
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
MIIDZTCCAk2gAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx
|
|
3
3
|
CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZ0
|
|
4
4
|
Y3BiaW4xDDAKBgNVBAsMA29wczETMBEGA1UEAwwKdGNwYmluLmNvbTEjMCEGCSqG
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
FOLuMowBSAZfV5v82LmlaIIOvU/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUxMDAyMTMzNjQ1WhcN
|
|
6
|
+
MjUxMDAzMTMzNjQ1WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
|
|
7
|
+
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK2xah/MsJKGnIpj84nYfBRCeAh8
|
|
8
|
+
MmN5dZwUCteehkX+wFhioEx1loAIqcqs6MPe7qQdeROuOZlGeiDR/6X0mnDsgyR4
|
|
9
|
+
qUnrIaRHS3EFWGtGp0WRLsQhKNvtxAHL7dErz7BoxTO5gV/w6dcT3uPORfZxkZdm
|
|
10
|
+
6t01t7DwPFktopQju9E8eeMBKVKaO1RrclZKkWEyfuHP3wNpIE6GwsH8jCn4I+r/
|
|
11
|
+
Y8Pgd+k4EyCaH+ngtFbS0lEhxxuqTpPRYyQrlEDMa1i/nETuMiL2WFkjEO2gEX/p
|
|
12
|
+
w05GlO3YUTDpaXWUWZf+Ck7MYevrFDC57oSZvMwTn5pJrQztvNJjFc+YF6kCAwEA
|
|
13
|
+
AaNCMEAwHQYDVR0OBBYEFLuHuKWOQ/U1+OpiGgffNV1faI9qMB8GA1UdIwQYMBaA
|
|
14
|
+
FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQBYIRlg6Paz
|
|
15
|
+
ALTKoBm8nRB19n4d7Ay+U1bUBCP2U5vZkUTSu05ttQXT72TaFIaT04pFs9uIHGFZ
|
|
16
|
+
9yhP2DgbuCaexQrvf9yf6nnoCqLjlhJ8Y0NmscK14Hp9GgVSnkXeweL82gFsXB45
|
|
17
|
+
FaqqyDn20QiH14nwU2H7g8y/Ys0PIemhjG8UT7OjSLyhkaCkxwYy88N/2YjX3hGH
|
|
18
|
+
9eTHvfMZ+9E7Urh3DevivhfPQnX/6lUAOK7Sr+WTIEjpZ89Lz/rLltRCXoXv75C4
|
|
19
|
+
PdRoxB2/lo+3+X1i4B7ujaduZDLaMyPcckbpuTYxjPZtoxhLyYkKgzQTUMwYsVrW
|
|
20
|
+
DoI6HuqlfzoH
|
|
21
21
|
-----END CERTIFICATE-----
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
-----BEGIN PRIVATE KEY-----
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
2
|
+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtsWofzLCShpyK
|
|
3
|
+
Y/OJ2HwUQngIfDJjeXWcFArXnoZF/sBYYqBMdZaACKnKrOjD3u6kHXkTrjmZRnog
|
|
4
|
+
0f+l9Jpw7IMkeKlJ6yGkR0txBVhrRqdFkS7EISjb7cQBy+3RK8+waMUzuYFf8OnX
|
|
5
|
+
E97jzkX2cZGXZurdNbew8DxZLaKUI7vRPHnjASlSmjtUa3JWSpFhMn7hz98DaSBO
|
|
6
|
+
hsLB/Iwp+CPq/2PD4HfpOBMgmh/p4LRW0tJRIccbqk6T0WMkK5RAzGtYv5xE7jIi
|
|
7
|
+
9lhZIxDtoBF/6cNORpTt2FEw6Wl1lFmX/gpOzGHr6xQwue6EmbzME5+aSa0M7bzS
|
|
8
|
+
YxXPmBepAgMBAAECggEBAIqgeMTch2jKyxGg6HTyNUWuL0MGbOj7vaROUsD4os4m
|
|
9
|
+
nrlsLegYSX/yaiF6k2QQ/4I4i7Prb8kneL3NHS8E5GaQPbLUIrj+UyFcTZfs3j7w
|
|
10
|
+
avyY/SxIEuZFBBUy/6HcR6zSUeIQgnNiQiAImfJTZX9l8P9Xgsf+4Zb0hhXe2E1G
|
|
11
|
+
TqLAYWnLoxD9us0rryQb37mRIREKcTO3qza+FXk32bWe83f1Dy46JJIChIoQg/QB
|
|
12
|
+
hwzSL51ld22fy7p+bsqeI3HUDWHplq4T0lws0SGUd4IvntF9rNGYHAI+aUKwiUBC
|
|
13
|
+
g/0se9+GBKeMSjhS11F9/Clg4i0E/tN/poOytA83aAECgYEA3+E0bhbaFAvzzevv
|
|
14
|
+
tAWSXkwHSFZ745KsJSJ8OOVkd+ATleyGl3AoRTrDxNqY+R/TvBFaTyiY+zqGV6tj
|
|
15
|
+
NI8SaSXuhRzbA9zDL2rSH7GIOeqzUs8PSUMk6JnmfCKk+aTBOoi8ULolHE4N6B45
|
|
16
|
+
te3GVgy/luJK22pPLCMsmL+D0ykCgYEAxpzr8T2CPVtl33W+loGbvrqZ1xXgq/7n
|
|
17
|
+
IsjHZdVdFsIIxFhtE2A2iO/7vMsLmwkZ7LgbtHcfEPOxjx5VzVt89l1I2lnP0jW+
|
|
18
|
+
pjKrN/eyXcHnWLLPCEtXA06oIvyem/VMSGXumu7EcOJZjnG3/PmqCWCjc2UUo2lJ
|
|
19
|
+
F2rQdC9bMIECgYEAl3nSdaI0j1e+79bw6kbSz8Z1LvaFAGce3klE72IV5h3QYqIU
|
|
20
|
+
NqaGOMEX8DtPQU/NfPPovKJlT6Y7e1nU15zuAgLOLXZmoWhfD9ggr5z45Obtydub
|
|
21
|
+
JiCt+ksW7WqrYNWef7JAaAZqUYpmUmUQ+w0UIuihQL9/kpGNW/m4lOkPknECgYAI
|
|
22
|
+
gbodWAwXAq4nVwy1t5FrJuTl8HryAvX1aHIZ63yUN/VWK49ocAuF6/l1SaESn94r
|
|
23
|
+
ZGtTXHLJMBbf0WXNaOi+SJqRN52OHF0xEySAPiy2lVKKWwZBDbEJZDoRXY6RkX0V
|
|
24
|
+
8L+6hRWG3DsHvdkqjar5wdjeXWr34M+PoDSTdV/LgQKBgFAqJc4I91AK8Ah9Ba8k
|
|
25
|
+
LqYtRFGFJ2J+p8QqcibfT6d6tZqWffxiXbQ5bS52/fmM3BdxEHjtzLdA7cOqCh6g
|
|
26
|
+
79kxRb3B5jjq5JvGtLy89VphZ08W9dhzgL/l3KGtgMobfTYUEDaNk33oExhItQnx
|
|
27
|
+
F97ecAMIx20oqFFOg6N0unwW
|
|
28
28
|
-----END PRIVATE KEY-----
|
|
@@ -45,9 +45,9 @@ def step_impl(context, varname, format_value, duration, duration_unit, value):
|
|
|
45
45
|
deltaT = timedelta(**args)
|
|
46
46
|
|
|
47
47
|
if value == "ago":
|
|
48
|
-
date =
|
|
48
|
+
date = DateTime.now() - deltaT
|
|
49
49
|
elif value == "ahead":
|
|
50
|
-
date =
|
|
50
|
+
date = DateTime.now() + deltaT
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
v = date.strftime(format_value)
|
|
@@ -92,12 +92,12 @@ class RMQManager:
|
|
|
92
92
|
# polling_seconds = 0.001 # a polling period below 1 ms is usually not efficient in testing context
|
|
93
93
|
#
|
|
94
94
|
# # Wait first message is needed
|
|
95
|
-
# dt_begin =
|
|
95
|
+
# dt_begin = DateTime.now()
|
|
96
96
|
# dt_last_poll = dt_begin
|
|
97
97
|
# if first_timeout_seconds is not None:
|
|
98
98
|
# while (dt_last_poll - dt_begin).total_seconds() < first_timeout_seconds:
|
|
99
99
|
# nb_msg_by_consumer = {c.name: c.nb_messages for c in consumers if c.nb_messages > 0}
|
|
100
|
-
# dt_last_poll =
|
|
100
|
+
# dt_last_poll = DateTime.now()
|
|
101
101
|
# if len(nb_msg_by_consumer) > 0:
|
|
102
102
|
# res = sum(nb_msg_by_consumer.values())
|
|
103
103
|
# if Tools.do_log(logger, logging.DEBUG):
|
|
@@ -115,7 +115,7 @@ class RMQManager:
|
|
|
115
115
|
# while (dt_last_poll - dt_last_receive).total_seconds() < window_seconds:
|
|
116
116
|
# nb_msg_by_consumer = {c.name: c.nb_messages for c in consumers if c.nb_messages > 0}
|
|
117
117
|
# nb = sum(nb_msg_by_consumer.values())
|
|
118
|
-
# dt_last_poll =
|
|
118
|
+
# dt_last_poll = DateTime.now()
|
|
119
119
|
# if nb > res:
|
|
120
120
|
# res = nb
|
|
121
121
|
# if Tools.do_log(logger, logging.DEBUG):
|