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.

Files changed (56) hide show
  1. holado/common/context/session_context.py +6 -5
  2. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/METADATA +1 -1
  3. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/RECORD +56 -41
  4. holado_core/common/block/base.py +3 -3
  5. holado_core/common/resource/persisted_data_manager.py +10 -10
  6. holado_core/common/resource/resource_manager.py +34 -37
  7. holado_core/common/resource/table_data_manager.py +110 -0
  8. holado_core/common/tools/path_manager.py +31 -6
  9. holado_django/server/django_projects/rest_api/rest_api/urls.py +1 -1
  10. holado_docker/tests/behave/steps/tools/docker_controller/client_steps.py +2 -2
  11. holado_docker/tools/docker_controller/client/rest/docker_controller_client.py +38 -1
  12. holado_docker/tools/docker_controller/server/run_docker_controller_in_docker.sh +3 -3
  13. holado_examples/tests/behave/testing_solution/environment.py +3 -3
  14. holado_examples/tests/behave/testing_solution/src/context/session_context.py +2 -2
  15. holado_examples/tests/behave/testing_solution/steps/config_steps.py +1 -1
  16. holado_multitask/multithreading/loopthread.py +4 -4
  17. holado_python/common/tools/datetime.py +37 -16
  18. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +16 -16
  19. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +26 -26
  20. holado_python/tests/behave/steps/standard_library/datetime_steps.py +2 -2
  21. holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +3 -3
  22. holado_report/__init__.py +1 -0
  23. holado_report/campaign/campaign_manager.py +194 -0
  24. holado_report/report/builders/detailed_scenario_failed_report_builder.py +11 -7
  25. holado_report/report/builders/failure_report_builder.py +4 -3
  26. holado_report/report/builders/short_scenario_failed_report_builder.py +6 -5
  27. holado_report/report/builders/summary_report_builder.py +1 -1
  28. holado_report/report/builders/summary_scenario_failed_report_builder.py +6 -5
  29. holado_report/report/builders/summary_scenario_report_builder.py +12 -11
  30. holado_report/report/execution_historic.py +4 -2
  31. holado_report/report/report_manager.py +65 -13
  32. holado_rest/api/rest/rest_client.py +5 -1
  33. holado_system/system/filesystem/file.py +5 -2
  34. holado_test/__init__.py +3 -0
  35. holado_test/common/context/feature_context.py +3 -3
  36. holado_test/common/context/scenario_context.py +3 -3
  37. holado_test/common/context/step_context.py +3 -3
  38. holado_test/test_server/client/rest/test_server_client.py +117 -0
  39. holado_test/test_server/server/Dockerfile +70 -0
  40. holado_test/test_server/server/core/server_context.py +42 -0
  41. holado_test/test_server/server/core/server_manager.py +54 -0
  42. holado_test/test_server/server/requirements.txt +2 -0
  43. holado_test/test_server/server/rest/README +2 -0
  44. holado_test/test_server/server/rest/api/__init__.py +23 -0
  45. holado_docker/tools/docker_controller/docker_controller_manager.py → holado_test/test_server/server/rest/api/campaign/__init__.py +10 -28
  46. holado_test/test_server/server/rest/api/campaign/scenario.py +33 -0
  47. holado_test/test_server/server/rest/initialize_holado.py +72 -0
  48. holado_test/test_server/server/rest/logging.conf +50 -0
  49. holado_test/test_server/server/rest/openapi.yaml +46 -0
  50. holado_test/test_server/server/rest/run.py +40 -0
  51. holado_test/test_server/server/run_test_server_in_docker.sh +101 -0
  52. holado_value/common/tools/unique_value_manager.py +2 -1
  53. test_holado/environment.py +1 -1
  54. test_holado/logging.conf +1 -0
  55. {holado-0.8.4.dist-info → holado-0.9.1.dist-info}/WHEEL +0 -0
  56. {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 = datetime.now()
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 find_files_starting_with(self, dir_path, prefix):
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
- if os.path.isfile(os.path.join(dir_path, filename)) and filename.startswith(prefix):
199
- res.append(os.path.join(dir_path, filename))
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 find_file_starting_with(self, dir_path, prefix):
203
- files = self.find_files_starting_with(dir_path, prefix)
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.docker_controller_manager import DockerControllerManager
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 = DockerControllerManager.new_client(**kwargs)
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
- # - HOLADO_DOCKER_CONTROLLER_NAME: name of the container
16
- # - HOLADO_DOCKER_CONTROLLER_PORT: REST API port to use (default: 8000)
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=8000
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 = datetime.datetime.now()
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 datetime.datetime.now() >= self.__next_loop_datetime
82
+ return DateTime.now() >= self.__next_loop_datetime
83
83
 
84
84
  def _get_sleep_time_before_next_loop(self):
85
- now = datetime.datetime.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 datetime.datetime.utcnow() - cls.utc_to_tai_timedelta()
88
+ return cls.now(TIMEZONE_UTC) - cls.utc_to_tai_timedelta()
81
89
  else:
82
- return datetime.datetime.utcnow()
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 datetime.datetime.utcnow()
95
+ return cls.now(TIMEZONE_UTC)
88
96
  else:
89
- return datetime.datetime.utcnow() + cls.utc_to_tai_timedelta()
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
- # if res.tzinfo is None:
137
- # res = res.replace(tzinfo=timezone.utc)
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
- SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwOTAxMTAxNDQ4WhcN
6
- MjUwOTAyMTAxNDQ4WjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
- DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHQeuEPAhoVTo8M3sAmA6gDvsz6
8
- 6dZ8UvhuvT8euQWB2p37nrNy+/DskwdR5NYdyrqyJ8ZxgO9UGcOi6Kg/xpv5R4Hv
9
- DKL5PJ1zVsG10pAY3zbAyVuw03S++tXhG910pTTUQyMEgXTxg18PBaA9ocW5VRCJ
10
- cbjQVVLikaI+YzeeOmDbgEBys0svG5W8oDoTh+AcoKp6Jpoxh5mZ2TrVXgP0IAZY
11
- 5I1S7HJLTtd7DOLiyWg3OydHNvvZaJ8MJPxZWtRqJbjRVfKbctgCgXDsH0YPHatX
12
- 48Z7gOK17LUFQYOa4FAcT1wlrxKW6jE8tDRO3W4lyr7S3mzrsshxBzCcz7cCAwEA
13
- AaNCMEAwHQYDVR0OBBYEFO3W/Yg4a3z1jH7AZOpc38UZy9onMB8GA1UdIwQYMBaA
14
- FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQAJ10bOcpm3
15
- /CqQSf2Lz1P0Fcf/I2o2TnCUgrkDDKUVOEK7txnpc6Djz9KxSJiePM/U9NYOzFX1
16
- 48qpynGOlA2m/tR+56RVCvTI8yPIqKWk/NGU7TpDfxrbY4DKCqEnDewiiEx4+aJg
17
- kwvoH4qClrt+PWY3NKpvFI9Bh1ilRTEB/bGqTwOZWMpv+s215SxywjXcXsZ49dFm
18
- gebdkuQVnD4VkSvH8SjyT28H0Yk/GyCfjzRdKPR8PilHnkJLlO9tmBTyFJ6eSwdZ
19
- fSxNqDyv9JwK7uGUZv5k4A7QZMLkSJNwAxyck8lLNW9YpY74wpaDqTB+ZUy8XNai
20
- jpB+z/vD71Qg
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
- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDB0HrhDwIaFU6P
3
- DN7AJgOoA77M+unWfFL4br0/HrkFgdqd+56zcvvw7JMHUeTWHcq6sifGcYDvVBnD
4
- ouioP8ab+UeB7wyi+Tydc1bBtdKQGN82wMlbsNN0vvrV4RvddKU01EMjBIF08YNf
5
- DwWgPaHFuVUQiXG40FVS4pGiPmM3njpg24BAcrNLLxuVvKA6E4fgHKCqeiaaMYeZ
6
- mdk61V4D9CAGWOSNUuxyS07Xewzi4sloNzsnRzb72WifDCT8WVrUaiW40VXym3LY
7
- AoFw7B9GDx2rV+PGe4Ditey1BUGDmuBQHE9cJa8SluoxPLQ0Tt1uJcq+0t5s67LI
8
- cQcwnM+3AgMBAAECggEAIIfVmCDkwWwCG5CVcrrusiyuwcj7pryHUJuFuCp386yv
9
- /QRWxtft4apTo07jkey+Mkfk1TIH6iO7j1KwqkzKETPuBKkS/efHfMX3MdxK01TZ
10
- Yl9JbNJpK5dCktON0cXaVNuryRzgfbhrzHDBCPxPQtiwpm3rYJHXQGo293xxOsxR
11
- MzVn4oGjQIxQc49QAs0Xgqxn38wUPcgej3vCtSlaUIDxTwfugEoQQ1x5n2D6k7Re
12
- 7gPPrHskIr9YuuqQlDzyJ3qVAI/HQiQPQbJzwWgtRVSb12/Yzb0gSMi86kMvn4Oy
13
- Zb3S+PSM+dKhMNLG/7QZHyc3R+Us3PMOnS/QiidvgQKBgQDp0zIluHEVcN6+wHsD
14
- pugM4A1Rll1VY/tu8OIwb7X8Tb24wIKQyA64FkAz6BVriXBN5gqqgXclE5PNh34b
15
- 0FVfv0t0Hm9aVzxKXlfDVEFzIMQammKHZuVyHILNxNqggnTC4zzqrxUlITzqb+kc
16
- 7l0rb8aoc7HYsxq8ZWv6l+5LbwKBgQDUMegGYQ8S6QhJeRoO12E7tDFSJVXhjW3Z
17
- jXsZaXgENI3d3wk2WUSQZK8D8fFoRM41ctfn9c4xm3vj1iDaGIYox76FCE4QyXez
18
- mvQoVPz+3lBiTBQTQpcI3XLZPH+rjdM4M7WsAxd5yGaW2r23+8WNDdEdGd+P0sQ4
19
- GnIEuyA8OQKBgQCuLY+2f41lbl3O9GzuTH+hT6k8NGk0ObVLcA7eC+khl/uSy3y3
20
- Qv+L669Ju0FxiMDqlkktuCE7sjiwqpSrPWbLWyFmz2Edh8w2jc6Mh4/1pdvPAWAS
21
- Avk6JrjddR1y4Zr+re1r2J5EuVy6OkEJNxqaR7pRM1Ww2cI5wsdLta9h6wKBgQCl
22
- aYIF9wNZKm9bVxwJFbymXgFUl+YqvaU0QiYD2UPFg3JT0tnLlCrupI4J9UDKl1ho
23
- Me481FC4OwyIvxM88IEkqRDLg33XeDXnvJM4HYha5dKjzcqYeQBj2Lz+uzqK8TQG
24
- S8kDHiXeuWm/jyT1lz6YhnzMwq6T3yk20uau6XBngQKBgCpM3x2XqCiPzlQ/da84
25
- 7SsCJ1jp4PJt1PCjFbp6jzPw9FD7lj1nSzGvVAEwKyL0OXq4k3+o9Nj/KYqncXzJ
26
- v8GmSyjEtSy1VynW8D1VAtCbfI7GHa+TflcjLqKb2BN+BpTGjeVPRbXVUlwqFF3I
27
- ughugjyndVciKdl7MmifCz92
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 = datetime.now() - deltaT
48
+ date = DateTime.now() - deltaT
49
49
  elif value == "ahead":
50
- date = datetime.now() + deltaT
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 = datetime.now()
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 = datetime.now()
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 = datetime.now()
118
+ # dt_last_poll = DateTime.now()
119
119
  # if nb > res:
120
120
  # res = nb
121
121
  # if Tools.do_log(logger, logging.DEBUG):
holado_report/__init__.py CHANGED
@@ -23,5 +23,6 @@ def register():
23
23
  SessionContext.instance().services.register_service_type("report_manager", ReportManager,
24
24
  lambda m: m.initialize(SessionContext.instance().multitask_manager))
25
25
 
26
+
26
27
 
27
28